Style Guides » Maintainable Patterns
Consistency, Clarity, Efficiency
document, ergo sum
My First Docs (2009)
// Susy Example:
#page
+grid-container
#left-nav
+grid-col(3)
#main-content
+grid-prefix(1)
+grid-col(4, 10, true)
WTF?!
No Programming Skillz
I make artsy bullshit
No Original Ideas
Thanks Natalie Downe!
No Plan
Just scratching an itch
No Stinkin’ Badges
seriously, badges weren’t a thing in 2009
35 Twitter Followers
no GitHub account
Context Matters
Team Size & Structure?
Internal vs. Consulting?
Similar Problems, Unique Constraints
your mileage will vary
Systems for System-Building
“Small Bootstrap something something every website [or something]”
— Not Brad Frost
(I’m great at taking notes)
"Tiny Bootstraps, for Every Client"
— Dave Rupert
(nailed it)
Our Product is the Architecture
a solid foundation for hand-off
Quality Architecture == Patterns in Code
documentation isn’t enough
Patterns Combine Languages
Design » HTML » CSS » JavaScript
Style Guides Represent Integration
show context & relationships
Maintenance Must be Integrated
process, toolkit, visibility, simplicity
“No Silos”
— Donna Chan & Isaak Hayes [from memory]
Pattern API
(Brad Frost said it, so I added a slide)
Basics of CSS Web Architecture
Atomic, ITCSS, SMACSS, OOCSS, BEM…
they’re all great for someone…
Separation of Concerns
data » logic » structure » presentation
Specificity is Your Guide
prefer the shallow end…
CSS Was Designed For Patterns
what did you think “classes” are for?
Don’t Repeat Yourself
but also:
Don’t Stretch For Patterns
"These Elements Share a Border Style"
too fragile
"These Elements Share a Purpose"
that purpose is represented by a border style
“Extends work well when used semantically to represent is-a relationships”
— Natalie Wiezenbaum [from memory]
Build One-Offs
— Nathan Curtis
Patterns Should Add Meaning
semantic naming helps
// before
aside {
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%
}
//after
aside {
width: span(3);
margin-right: gutter();
padding-left: span(1 wide);
}
(you probably don’t need grids)
Naming Conventions
consistent across the entire team
What Is This?
layout region » component » element » state » JS-hook
-
layout region »
data-region="banner"
-
component »
.calendar
ordata-calendar="weekly"
? -
element »
.calendar-day
? -
state »
data-state="active"
or.is-active
-
JS-hook »
.js-calendar
No Right Answer
but “no answer” is TOTALLY WRONG
Abstract Patterns
color palettes, sizes, fonts…
How Do We Represent Abstract Patterns?
Color Variables
$color--brand-orange: hsl(24, 100%, 39%);
$color--brand-blue: hsl(195, 85%, 35%);
$color--brand-pink: hsl(330, 85%, 48%);
.usage {
background: $color--brand-pink;
}
easy to access, forced grouping, not machine readable
Color Maps
$colors: (
'brand-orange': hsl(24, 100%, 39%),
'brand-blue': hsl(195, 85%, 35%),
'brand-pink': hsl(330, 85%, 48%),
);
.usage {
background: map-get($colors, 'brand-pink');
}
naturally grouped, accessible through functions, machine readable
The Map Problem
$colors: (
'brand-blue': hsl(195, 85%, 35%),
'gray': desaturate(map-get($colors, 'brand-blue'), 80%),
);
[ERROR] Undefined variable: "$colors".
Define Now, Calculate Later
$colors: (
'brand-blue': hsl(195, 85%, 35%),
'gray': 'brand-blue' ('desaturate': 80%),
);
// Calculate Later:
.usage {
background: color('gray');
}
OddBird’s Accoutrement-Color
Semantic Color Assignments
$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',
// ...
);
What Are The TradeOffs?
Make Documentation 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%),
);
Size Maps
$text-sizes: (
'root': 22px,
'small': 18px,
'smaller': 16px,
);
Accoutrement-Scale
$ratios: (
'herman': 1.4
);
$text-sizes: (
'root': 22px,
'h1': 'root' ('herman': 3),
'h2': 'root' ('herman': 2),
'h3': 'root' ('herman': 1),
'small': 18px,
'smaller': 16px,
);
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',
);
Toolkits Are A Byproduct
Systems > Solutions
provide tools, not pre-built structures
Tools Should Fit You
“Sorry, this hammer only builds patio chairs…”
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;
}
}
Test Your Tools
Sass True
@include test('Merges two maps, adding their values where appropriate') {
$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');
}
True Results
Lint Your Code
let the robots do the work
Sass-Lint Results
Concrete Patterns
icons, buttons, etc.
HTML Templates (Jinja/Nunjucks)
pre-processors for your markup!
Patterns » Macros
<h1>{{ icon('wave') }} Hello World!</h1>
Template Logic is Great
presentation layer should have opinions
{% set icon = 'ok' if ('success' in tags) else 'warning' %}
Templates Should Look Like Templates
I want to see the expected structure at a glance
Lightning Design: Warning Icon
<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>
Related Class Patterns » Data Attributes
icon color and size classes
[data-slds-icon-color]
.slds-icon-text-default
| .slds-icon-text-warning
| .slds-icon-text-error
data-slds-icon-color="text-default | text-warning | text-error"
[data-slds-icon-size]
.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 %}
<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>
{{ icon(‘warning’) }}
Sass Icon Defaults
.slds-icon_container {
border-radius: size('border-radius');
display: inline-block;
}
.slds-icon {
@include square('medium');
border-radius: size('border-radius');
fill: color('white');
}
Sass Icon Colors
@each $icon-color in ('text-default', 'text-warning', 'text-error') {
[data-slds-icon-color='#{$icon-color}'] {
fill: color($icon-color);
}
}
Sass Icon Sizes
@each $icon-size in ('extra-small', 'small', 'medium', 'large') {
[data-slds-icon-size='#{$icon-size}'] {
@include square($icon-size);
}
}
Herman Icons
@icons <icon-folder> <icon-macro-file>:<macro>
/// @icons icons/ utility.macros.js.j2:icon
.slds-icon {
@include square('medium');
border-radius: size('border-radius');
fill: color('white');
}
Something More Complex?

Here’s A Macro
it creates variations on a user-message
{% macro user_message(message, tags=[]) %}
{% if message %}
<div data-message="{% for tag in tags %}{{ tag }} {% endfor %}">
{# THINGS GO HERE. YOU DON'T CARE. #}
</div>
{% endif %}
{% endmacro %}
So… Now What?
Maybe?..
{% set user_message_doc={
'name': 'User Message',
'description': 'Generate pop-up user notification messages for info, success, errors, or warnings.',
'examples': [
{
'content': 'Yay, that worked great!',
'tags': ['success']
},
{
'content': 'Oh no, something went wrong!',
'tags': ['error']
}
]
} %}
{% macro user_message(message, tags=[]) %}
{# THINGS GO HERE. YOU DON'T CARE. #}
{% endmacro %}
And/Or This?
/// Generate pop-up user notification messages for info, success, errors, or warnings.
/// @macro message.macros.js.j2:user_message
/// @macro-data
/// - 'content': 'Yay, that worked great!'
/// - 'tags': ['success']
/// @macro-data
/// - 'content': 'Oh no, something went wrong!',
/// - 'tags': ['error']
[data-message] {
content: "it's late, and I don't want to make a real example here";
}
StyleGuide!
Failing Our Way To Success
(maybe)