Style Guides | Design Systems | Pattern Libraries
everybody’s doing it…
(…are you?)
Nerd.
How do I Build It?
Code Patterns Are Design Patterns
You are not MailForce. You are not InstaFace.
(are you?)
(Team Size)
(Team Structure)
(Internal vs. Consulting)
(Is it 1970?)
Similar Problems
Unique Constraints
See the Pen It depends by Rachel Smith (@rachsmith) on CodePen.
by Rachel Smith
Small Team, Outside Consulting
Our Product is Good Architecture
patterns for hand-off
“Tiny Bootstraps, for Every Client”
— Dave Rupert
Consistent Systems
Custom Patterns
Web Patterns Start With Code
.grid-span {
width: ((3*4em) + (2*1em)) / ((12*4em) + (11*1em)) * 100%; // 23.7288136%
margin-right: 1em / ((12*4em) + (11*1em)) * 100%; // 01.6949153%
padding-left: ((1*4em) + (1*1em)) / ((12*4em) + (11*1em)); // 08.4745763%
}
Patterns Add Meaning
.grid-span {
width: span(3);
margin-right: gutter();
padding-left: span(1 wide);
}
(you probably don’t need grids)
Something Like “Code is Communication” or Something
- Sarah Drasner
Build Pattern APIs
CSS Was Designed For Patterns
Classes are Patterns
The Cascade is for Patterns
Specificity is Your Guide
Normal Triangle by Maslow Miriam
(What’s missing? Layouts? State modifiers?)

Don’t Repeat Yourself
“DRY”
Don’t Stretch For Patterns
“DSfP?”
Not A Pattern:
“These Elements Share a Border Style”
Patterns Add Meaning:
“These Elements Share a Purpose”
…represented by a border style…
Build One-Offs
Home Page?
Patterns Combine Languages
Design » HTML (templates?) » CSS (preprocessor?) » JavaScript
Style Guides == Integration
context & relationships
Maintenance Must be Integrated
process, toolkit, visibility, simplicity
Naming Conventions
consistent across the entire team
Atomic, ITCSS, SMACSS, OOCSS, BEM…
they’re all great for someone…
See the Pen It depends by Miriam Suzanne (@mirisuzanne) on CodePen.
(by Rachel and Miriam)
No Right Answer
but “no answer” is TOTALLY WRONG


Abstract Patterns (in code)
color palettes, sizes, fonts…
CSS Variables?
:root {
--brand-orange: hsl(24, 100%, 39%);
--brand-blue: hsl(195, 85%, 35%);
--brand-pink: hsl(330, 85%, 48%);
}
Sass Variables?
$color--brand-orange: hsl(24, 100%, 39%);
$color--brand-blue: hsl(195, 85%, 35%);
$color--brand-pink: hsl(330, 85%, 48%);
Maps Add Meaning
$colors: (
'brand-orange': hsl(24, 100%, 39%),
'brand-blue': hsl(195, 85%, 35%),
'brand-pink': hsl(330, 85%, 48%),
);
The Map Problem
$colors: (
'brand-blue': hsl(195, 85%, 35%),
'gray': desaturate(map-get($colors, 'brand-blue'), 80%),
);
The Map Problem
$colors: (
'brand-blue': hsl(195, 85%, 35%),
'gray': desaturate(map-get($colors, 'brand-blue'), 80%),
);
[ERROR] Undefined variable: "$colors".
Map Self-Reference
$icons: (
'brand-pink': hsl(330, 85%, 48%),
'escher': 'brand-pink',
'godel': 'escher',
'bach': 'godel',
'kevin bacon': 'bach',
);
Define Now
$colors: (
'brand-blue': hsl(195, 85%, 35%),
'gray': 'brand-blue' ('desaturate': 80%),
);
OddBird’s Accoutrement-Color
Calculate Later
.usage {
background: color('gray'); // the function does all the work...
}
OddBird’s Accoutrement-Color
Map Self-Reference
$icons: (
'brand-pink': hsl(330, 85%, 48%),
'escher': 'brand-pink',
'godel': 'escher',
'bach': 'godel',
'kevin bacon': 'bach',
);
color('kevin bacon')
but Miriam,
It’s Over-Engineered!
See the Pen It depends by Miriam Suzanne (@mirisuzanne) on CodePen.
(by Rachel and Miriam)
Know The Trade-Offs
Meaningful to Humans & Machines
@each $color in map-keys($colors) {
[data-color='#{$color}'] {
background-color: color($color);
}
}
Accessed Dynamically
// this works:
@mixin background($color-name) {
background: color($color-name);
}
// this doesn't work:
@mixin background($color-name) {
background: $#{$color-name};
}
Layers of Abstraction
$brand-colors: (
'brand-orange': hsl(24, 100%, 39%),
'brand-blue': hsl(195, 85%, 35%),
'brand-pink': hsl(330, 85%, 48%),
);
$neutral-colors: (
'light-gray': 'brand-blue' ('tint': 80%, 'desaturate': 80%),
'gray': 'brand-blue' ('desaturate': 80%),
'black': 'brand-blue' ('shade': 65%),
);
$theme-colors: (
'text': 'black',
'border': 'gray',
// ...
);
Exportable
we use sass-json-export
// sass
$colors: (
'brand-orange': hsl(24, 100%, 39%),
'brand-blue': hsl(195, 85%, 35%),
'brand-pink': hsl(330, 85%, 48%),
);
@include json-encode($colors);
/* css */
/*! json-encode: '{
'brand-orange': 'hsl(24, 100%, 39%)',
'brand-blue': 'hsl(195, 85%, 35%)',
'brand-pink': 'hsl(330, 85%, 48%)'
}' */
Make Patterns
The Lazy Option
SassDoc + Herman (alpha)
// Brand Colors
// ------------
/// We use bright primary colors for the main brand,
/// everything else is based on these colors.
/// @preview color-palette
/// @group colors
$brand-colors: (
'brand-orange': hsl(24, 100%, 39%),
'brand-blue': hsl(195, 85%, 35%),
'brand-pink': hsl(330, 85%, 48%),
);
Modular Scales?
$ms-base: 1em;
$ms-ratio: $golden; // do you believe in magic?
$large: ms(1);
$larger: ms(2);
Named Ratios
$ratio-options: (
'octave' : 2,
'major-seventh' : 15/8,
'major-sixth' : 5/3,
'fifth' : 3/2,
'augmented-fourth' : 45/32,
'fourth' : 4/3,
'major-third' : 5/4,
'major-second' : 9/8,
);
Size Maps
$text-sizes: (
'root': 22px,
'small': 18px,
'smaller': 16px,
);
Accoutrement-Scale
$text-sizes: (
'root': 16px,
'rhythm': 'root' ('fifth': 1),
'gutter': 'rhythm',
);
p {
margin-bottom: size('gutter');
}
OddBird’s Accoutrement-Scale
Accoutrement-Type
$body-font: (
'name': 'CenturyOldStyle',
'source': 'http://www.fontspring.com/fonts/fontsite/century-old-style-fs',
'stack': ('Baskerville', 'Palatino', 'Cambria', 'Georgia', serif),
'regular': 'serif/CenturyOldStyle-regular',
'italic': 'serif/CenturyOldStyle-italic',
'bold': 'serif/CenturyOldStyle-bold',
);
Automated Webfont Imports
@include import-webfonts($fonts);
:root {
@include font-family('body');
}
Breakpoint Maps
$breakpoints: (
'footer-columns': 25em,
'toolbar-icons': 34em,
'form-columns': toolbar-icons,
'off-canvas': 50em,
'calendar-tabs': 60em,
);
Named Queries
.sidebar {
@include below('off-canvas') {
transform: translate3d(-101%, 0, 0);
}
}
It’s only over-engineered if it
Gets in Your Way
Part of the Process
CSS-Only Tabs
- Container wraps all tabs dynamically
- Container remains equal-height
- No JS Height calculations
- The entire thing is responsive
@mixin tabs(
$slugs,
$nested: $default-nested-selectors,
$checked: $default-checked-selector
) {
$nested-tabs: $nested;
$nested-content: $nested;
@if length($nested) > 1 {
$nested-tabs: nth($nested, 1);
$nested-content: nth($nested, 2);
}
@each $slug in $slugs {
$toggle : '[#{$toggle-attribute}*="#{$slug}"]';
$title : '[#{$title-attribute}*="#{$slug}"]';
$content : '[#{$content-attribute}*="#{$slug}"]';
#{$content} { @extend %hide-tab-content; }
#{$toggle} {
@extend %hide-tab-toggle;
&#{$checked} {
& ~ #{$nested-tabs} #{$title} {
@extend %active-tab-title !optional;
}
& ~ #{$nested-content} #{$content} {
@extend %show-tab-content;
}
}
}
}
}
What’s Essential?
@mixin input-toggle {
@include is-hidden;
&:checked {
@content;
}
}
Toolkits Are A Byproduct
Toolkits Provide TradeOffs
avoid opinionated output / stay abstract
Tools Should Fit You
“Sorry, this hammer only builds patio chairs…”
Test Your Logic
Try True
@include test-module('Utilities') {
@include test('Map Add [function]') {
$base: (one: 1, two: 1, three: 1);
$add: (one: 1, two: 2, three: -1);
@include assert-equal(
map-add($base, $add),
(one: 2, two: 3, three: 0),
'Returns the sum of two numeric maps');
}
}
Concrete Patterns
icons, buttons, etc.
Document, Ergo Sum
What Susy Functions?
Notification with Icons
HTML Template Logic is Great
pre-processors for your markup!
{% set icon = 'ok' if ('success' in tags) else 'warning' %}
Template Language Gotchas
(lessons from Sass language design)
1. Provide a Super-Set
2. Clearly Distinguish New Syntax
<Icon name="gear" alt="alternative text" size="16" className="foo" data-other="bar" />
(not clear, not super)
<p>{{ icon('gear', alt='alternative text', class='foo') }}<p>
Looks like Markup
{% macro icon(name, alt=none, size=none, is_inline=true, class=none) %}
<svg data-icon="{{ name }}" {{ attr_if(size, 'data-size') }} {{ attr_if(class, 'class') }}>
<use xlink:href="#icon-{{ name }}" />
</svg>
{%- if alt -%}
<span class="is-hidden">{{ alt }}</span>
{%- endif -%}
{% endmacro %}
What the ??
import React from 'react';
const Icon = React.createClass({
/* Render an SVG icon with or without text.
*
* Sample use from another component's `render` method:
* return <Icon name="gear" alt="Could mean anything" size="16" className="foo" data-other="bar" />
*/
render () {
let {name, alt, size, is_inline, className, attrs} = this.props;
if (is_inline === undefined) {
is_inline = true;
}
return (
<span>
<svg data-icon={name} data-is-inline={is_inline} data-size={size}
className={className} {...attrs}><use xlinkHref={'#icon-' + name} /></svg>
{alt ? <span className="js-icon-text is-hidden">{alt}</span> : ''}
</span>
);
}
});
export default Icon;
JavaScript is Not a Universal Solution
other languages exist for a reason
class="slds-icon
slds-icon-text-warning
slds-icon--x-small"
Related Class Patterns » Data Attributes
icon color and size classes
.slds-icon-text-default
| .slds-icon-text-warning
| .slds-icon-text-error
data-slds-icon-color="text-default | text-warning | text-error"
.slds-icon--x-small
| .slds-icon--small
| .slds-icon--medium | .slds-icon--large
data-slds-icon-size="extra-small | small | medium | large"
Jinja2: Icon Macros
{{ icon(name, alt, color, size) }}
Jinja2: Icon Macros
{% macro icon(name, alt=none, color='text-default', size='medium') %}
<span class="slds-icon_container">
<svg aria-hidden="true" class="slds-icon" data-slds-icon-color="{{ color }}" data-slds-icon-size="{{ size }}">
<use xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#{{ name }}"></use>
</svg>
<span class="slds-assistive-text">{{ alt or name }}</span>
</span>
{% endmacro %}
{{ icon(‘warning’) }}
vs.
<span class="slds-icon_container">
<svg aria-hidden="true" class="slds-icon slds-icon-text-warning slds-icon--x-small">
<use xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#warning"></use>
</svg>
<span class="slds-assistive-text">Warning Icon</span>
</span>
Dynamic Colors & Sizes
@each $icon-color in ('text-default', 'text-warning', 'text-error') {
[data-slds-icon-color='#{$icon-color}'] {
fill: color($icon-color);
}
}
@each $icon-size in ('extra-small', 'small', 'medium', 'large') {
[data-slds-icon-size='#{$icon-size}'] {
@include square($icon-size);
}
}
Herman Icons
/// @icons icons/ utility.macros.js.j2:icon
.slds-icon {
@include square('medium');
border-radius: size('border-radius');
fill: color('white');
}
/// @icons <icon-folder> <icon-macro-file>:<macro>
Notification with Icons
Notification Macro
with arguments, like a Sass mixin
{% macro notice(message, tags=[]) %}
{% if message %}
<div data-notice="{% for tag in tags %}{{ tag }} {% endfor %}">
{% set icon_name = 'ok' if ('success' in tags) else 'warning' %}
{{ icon(icon_name) }}
{# THINGS GO HERE. YOU DON'T CARE. #}
</div>
{% endif %}
{% endmacro %}
Herman Documentation
/// @example njk
/// {% import 'alert.macros.njk' as alert %}
/// {{ alert.notice(
/// message='You did it! You win all the style guides!',
/// tags=['success']
/// ) }}
[data-notice] {
/* ... notification styles here ... */
}