Jump Start Sass

Debate Prep


Debate Backdrop

What Zen was to the 70’s (most famously with motorcycle maintenance), the Tao Te Ching was to the 90’s. From Piglet and Pooh to Physics and back, many have sought sense in applying the Tao Te Ching to something (the Tao of Physics), or something to the Tao Te Ching (the Tao of Pooh). It can be a cheap trick, but lately it has struck me that there is more than a little to be understood about web design by looking through the prism of the Tao. no comments Share this on Translations Russian French Record & analyze live sessions of your users Ad via BuySellAds JOB BOARD PhishMe is looking for a Senior Software Developer. Job listings via We Work Remotely Daoism is a philosophy, like Buddhism, a way of living, of being in the world, which stems from a text of great antiquity, the Tao Te Ching, whose 81 “chapters” enigmatically sweep across human experience, but with a strong common theme, that of harmony. For the last couple of years, for better or worse, my life has revolved more than a little around style sheets. I write software, tutorials, and guides for them; I’ve answered too many questions to count about them on newsgroups and via email; I’ve fought for their adoption with The Web Standards Project. And slowly I’ve come to understand web design entirely differently because of them, and to see a strong association between design and the Tao. What I sense is a real tension between the web as we know it, and the web as it would be. It’s the tension between an existing medium, the printed page, and its child, the web. And it’s time to really understand the relationship between the parent and the child, and to let the child go its own way in the world. Same old new medium? Well established hierarchies are not easily uprooted; Closely held beliefs are not easily released; So ritual enthralls generation after generation. Tao Te Ching; 38 Ritual If you’ve never watched early television programs, it’s instructive viewing. Television was at that time often referred to as “radio with pictures,” and that’s a pretty accurate description. Much of television followed the format of popular radio at that time. Indeed programs like the Tonight Show, with its variants found on virtually every channel in the world (featuring a band, the talk to the camera host, and seated guests), or the news, with the suited sober news reader, remain as traces of the medium television grew out of. A palimpsest of media past. Think too of the first music videos (a few of us might be at least that old). Essentially the band miming themselves playing a song. Riveting. When a new medium borrows from an existing one, some of what it borrows makes sense, but much of the borrowing is thoughtless, “ritual,” and often constrains the new medium. Over time, the new medium develops its own conventions, throwing off existing conventions that don’t make sense. If you ever get the chance to watch early television drama you’ll find a strong example of this. Because radio required a voice-over to describe what listeners couldn’t see, early television drama often featured a voice over, describing what viewers could. It’s a simple but striking example of what happens when a new medium develops out of an existing one. The web is a new medium, although it has emerged from the medium of printing, whose skills, design language and conventions strongly influence it. Yet it is often too shaped by that from which it sprang. “Killer Web Sites” are usually those which tame the wildness of the web, constraining pages as if they were made of paper – Desktop Publishing for the Web. This conservatism is natural, “closely held beliefs are not easily released,” but it is time to move on, to embrace the web as its own medium. It’s time to throw out the rituals of the printed page, and to engage the medium of the web and its own nature. This is not for a moment to say we should abandon the wisdom of hundreds of years of printing and thousands of years of writing. But we need to understand which of these lessons are appropriate for the web, and which mere rituals. Controlling web pages [The Sage] ... accepts the ebb and flow of things, Nurtures them, but does not own them Tao Te Ching; 2 Abstraction Spend some time on web design newgroups or mailing lists, and you’ll find some common words and ideas repeated time after time. Question after question, of course, is “how do I?”. But beneath questions like “how do I make my pages look the same on every platform” and “how can I make my fonts appear identical on the Macintosh and Windows” is an underlying question – “how do I control the user’s browser?” Indeed, the word control turns up with surprising frequency. Underpinning all this is the belief that designers are controllers (think about the implications of the term “pixel mechanic”). Designers want to override the wishes of users, and the choices that they have made about their viewing experience (by “fixing” font size, for instance). Designers want to second guess platform differences, caused by different logical resolutions (for instance the Macintosh’s 72dpi, versus the standard Windows 96dpi). Designers are all-knowing, and will not tolerate anything less than a rendering on every browser that is pixel perfect with the rendering on their own machine. .... Firstly, it guarantees that it is essentially impossible to have text look identical on Macintoshes and Windows based systems. But if you embrace the adaptability philosophy it doesn’t matter. What? If you are concerned about exactly how a web page appears this is a sign that you are still aren’t thinking about adaptive pages. One of the most significant accessibility issues is font size. Small fonts are more difficult to read. For those of us with good eyesight, it can come as a shock that a significant percentage of the population has trouble reading anything below 14 point Times on paper. Screens are less readable than paper, because of their lower resolution. Does that mean the minimum point size we should use is 14 pts? That doesn’t help those whose sight is even less strong.

Sass Maps?

I’ve Got The Best Sass Maps

No one has Sass maps like mine.

PRIVATE NOTE:


[Dodge The Question]

[Ramble about Design Systems]


DON"T MAKE A SLIDE ABOUT IT

Lightning Design System

Mail Chimp

Lonely Planet

Numbers

NYC Transit Authority Graphics Standards Manual

Graphics Standards Manual: 1970

Sign Construction and Engineering

How Do You Build It?

Sara Drasner

Context Matters

Internal vs Consulting

(tooling is different when you move quickly)

Are You InstaFace?

(bootstrap wasn’t designed for everyone)

Is it 1970?

Brian Jordan

OddBird

New OddBird

OddBird (Consulting)

Our Product is Good Architecture

patterns for hand-off

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);
}

Something Like “Code is Communication” or Something

- Sarah Drasner

CSS is for Pattern-Making

what did you think “classes” were for?

“The cascade is your friend!”

- Anonymous

(it was Sara Soueidan)


Sass

Meaningful Abstractions

Theme Configuration

Color Palette

Real Variables

Individual Variables

// individual definitions
$color--brand-orange: hsl(24, 100%, 39%);
$color--brand-blue: hsl(195, 85%, 35%);
$color--brand-pink: hsl(330, 85%, 48%);

Meaningful Maps

// grouped definitions
$colors: (
  'brand-orange': hsl(24, 100%, 39%),
  'brand-blue': hsl(195, 85%, 35%),
  'brand-pink': hsl(330, 85%, 48%),
);

Maps: Variable Data Type

like numbers, strings, and lists

$variable: (
  key: value,
  another key: another value,
);

Familiar Objects

// javascript
var myObject = {
  "Sally Smart": "555-9999",
  "John Doe": "555-1212",
};

Remember CSS?

.selector {
  background: white;
  border: 1em solid pink;
}

MapCSS!

$variable: (
  background: white,
  border: 1em solid pink,
);

Anything Goes

I dare you.

$OMG-STOP: (
  14 - floor(13/2): 'math',
  ❄: (
    'first': 'Miriam', 
    'last': 'Suzanne',
    'adr': (
      'region': 'CO',
      'locality': 'Denver'
    ),
  ),
  (one, two, three): $numbers,
  (pass: 0, fail: 12): 'All your tests failed!',
);

Anything Goes

I dare you.

$OMG-STOP: (
  14 - floor(13/2): 'math',
  ❄: (
    'first': 'Miriam', 
    'last': 'Suzanne',
    'adr': (
      'region': 'CO',
      'locality': 'Denver'
    ),
  ),
  (one, two, three): $numbers,
  (pass: 0, fail: 12): 'All your tests failed!',
);
// input
$bad-idea: map-get($OMG-STOP, 8);

// result
$bad-idea: 'math';

Accessing Maps

map-get($map, $key)

$colors: (
  'brand-orange': hsl(24, 100%, 39%),
  'brand-blue': hsl(195, 85%, 35%),
  'brand-pink': hsl(330, 85%, 48%),
);

:root {
  background: map-get($colors, 'brand-blue');
}

Adding & Editing & Merging

map-merge($map1, $map2)

$cools: (
  'blue': hsl(195, 85%, 35%),
);
$warms: (
  'pink': hsl(330, 85%, 48%),
);
$colors: map-merge($cools, $warms);

Adding & Editing & Merging

map-merge($map1, $map2)

$cools: (
  'blue': hsl(195, 85%, 35%),
);
$warms: (
  'pink': hsl(330, 85%, 48%),
);
$colors: map-merge($cools, $warms);
$colors: (
  'blue': hsl(195, 85%, 35%),
  'pink': hsl(330, 85%, 48%),
);

Adding & Editing & Merging

map-merge($map1, $map2)

$brand: (
  'blue': hsl(195, 85%, 35%),
  'pink': hsl(330, 85%, 48%),
);
$theme: (
  'pink': #000,
);
$colors: map-merge($brand, $theme);

Adding & Editing & Merging

map-merge($map1, $map2)

$brand: (
  'blue': hsl(195, 85%, 35%),
  'pink': hsl(330, 85%, 48%),
);
$theme: (
  'pink': #000,
);
$colors: map-merge($brand, $theme);
$colors: (
  'blue': hsl(195, 85%, 35%),
  'pink': #000,
);

Dynamic Variables

$colors: (
  'brand-orange': hsl(24, 100%, 39%),
  'brand-blue': hsl(195, 85%, 35%),
  'brand-pink': hsl(330, 85%, 48%),
);
// Add complements automatically!
@each $name, $color in $colors {
  $complement: (
    '#{$name}--complement': complement($color)
  );

  $colors: map-merge($colors, $complement);
}

Dynamic Variables

$colors: (
  'brand-orange': hsl(24, 100%, 39%),
  'brand-blue': hsl(195, 85%, 35%),
  'brand-pink': hsl(330, 85%, 48%),
);
@each $name, $color in $colors {
  $complement: (
    '#{$name}--complement': complement($color)
  );

  $colors: map-merge($colors, $complement);
}
$colors: (
  'brand-orange': hsl(24, 100%, 39%),
  'brand-blue': hsl(195, 85%, 35%),
  'brand-pink': hsl(330, 85%, 48%),
  'brand-orange--complement': #0077c7,
  'brand-blue--complement': #a5330d,
  'brand-pink--complement': #12e27a,
);

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',
);

Recursive Function

@function color(
  $name,
  $map: $colors
) {
  $name: map-get($map, $name) or $name;

  // Self-calling lookup
  @if map-has-key($colors, $name) {
    $name: color($name);
  }

  @return $name;
}

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

color('kevin bacon')

Soueidan

but Miriam,

It’s Over-Engineered!

Hacks

Know The Trade-Offs

There’s more than one right way

Meaningful to Humans & Machines

@each $name, $color in map-keys($colors) {
  [data-color='#{$name}'] {
    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};
}

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%)'
}' */


Herman Colors

Modular Scales?

$ms-base: 1em;
$ms-ratio: $golden; // do you believe in magic?

$large: ms(1);
$larger: ms(2);

Keith Grant

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,
);

Accoutrement-Scale

$text-sizes: (
  'root': 16px,
  'rhythm': 'root' ('fifth': 1),
  'gutter': 'rhythm',
);

p {
  margin-bottom: size('gutter');
}

OddBird’s Accoutrement-Scale

Accoutrement-Type

$fonts: (
  'body': (
    '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',
  ),
);

// automatically import fonts
@include import-webfonts($fonts);

:root {
  @include font-family('body');
}

OddBird’s Accoutrement-Type


Herman Fonts

Is It Over-Engineered?

Sass True (unit tests)

True Results

Maps as Data Storage

(and other things you probably don’t need)

Testing Sass

(useful for functions & mixins, not for styles)

@include test-module('Utilities') {
  @include test('Bad Example') {
    @include assert-equal(
      9/3, 
      3, 
      'Nine divided by three is equal to three');

    @include assert-unequal(
      9/3, 
      2, 
      'Nine divided by three is not two');
  }
}

True Database

$_true-results: (
  run: 0,
  pass: 0,
  fail: 0,
);

Storing Results

@mixin _true-update($result) {
  $new-results: (
    run: 1,
    $result: 1,
  );

  $_true-results: _true-map-increment($_true-results, $new-results) !global;
}

True Output

@debug 'total tests: ' map-get($_true-results, 'run');
@debug 'passed: ' map-get($_true-results, 'pass');
@debug 'failed: ' map-get($_true-results, 'fail');

Susy

No Grids

Susy Configuration

// factory defaults (don't touch)
$susy-defaults: (
  'columns': 4,
  'gutters': 0.25,
  'spread': 'narrow',
  'container-spread': 'narrow',
);
// user defaults
$susy: () !default;

// inline overrides
.conference {
  width: span(3, ('gutters': 0.5));
}

Current Settings

map-merge, all the way down

@function susy-settings(
  $overrides...
) {
  // merge user defaults with factory defaults
  $settings: map-merge($susy-defaults, $susy);

  // merge in any number of local overrides
  @each $config in $overrides {
    $settings: map-merge($settings, $config);
  }

  @return $settings;
}

On-The-Fly Configurations

define multiple grid-maps to call as needed

$susy: (
  'columns': 4,
  'gutters': 0.5,
);

$medium: (
  'columns': 8,
);

$large: (
  'columns': 12,
  'gutters': 0.25,
);
.grid-span {
  width: span(3);

  @media (min-width: 40em) {
    width: span(3, $medium);
  }

  @media (min-width: 60em) {
    width: span(3, $large);
  }
}

Language Parsing

.arguments {
  width: span(3, 12, $location: 1, $container-spread: wide);
}

.keywords {
  width: span(first 3 of 12 wide);
}

Map of Keywords

(match words to settings)

// setting: keywords
$options: (
  'location': 'first' 'alpha' 'last' 'omega',
  'spread': 'narrow' 'wide' 'wider',
);

Map of Keywords

(use the right one)

// looks sooo much better
$options: (
  'location': 'first' 'alpha' 'last' 'omega',
  'spread': 'narrow' 'wide' 'wider',
);
// simpler lookup & better performance
$options: (
  'first': 'location',
  'last': 'location',
  'alpha': 'location',
  'omega': 'location',
  'narrow': 'spread',
  'wide': 'spread',
  'wider': 'spread',
);

Basic Parser

@function parse(
  $shorthand, 
  $options
){
  $return: ();

  @each $item in $shorthand {
    @if map-has-key($options, $item) {
      $setting: map-get($options, $item);
      $new: ($setting: $item);
      $return: map-merge($return, $new);
    }
  }

  @return $return;
}

Basic Parser

// config
$options: (
  'first': 'location',
  'last': 'location',
  'narrow': 'spread',
  'wide': 'spread',
  'wider': 'spread',
);
// input
$test: parse('first' 'wider', $options);

// result
$test: (
  'location': 'first',
  'spread': 'wider',
);

What is Spread?

Spread Demo

Setting Normalization

Keywords => Usable Values

@function susy-normalize-spread(
  $spread
) {
  // number of gutters relative to columns
  $normal-spread: (
    'narrow': -1,
    'wide': 0,
    'wider': 1,
  );

  @return map-get($normal-spread, $spread) or $spread;
}
// input
$spread: susy-normalize-spread('wide');

// result
$spread: 0;

Parsed + Merged + Normalized

// input
$config: span(last 3 of 6 wide x 140px);

// result
$config: (
  'span': 3,
  'columns': (140px 140px 140px 140px 140px 140px),
  'gutters': 0.25,
  'spread': -1,
  'container-spread': 0,
  'location': 4,
);

Building CSS Maps

Chris Eppstein wishes I wouldn’t

@function get-gutters($width, $position) {
  $gutters: (
    'margin-left': null,
    'margin-right': null,
  );

  @if ( $position == 'split' ) {
    $gutters: (
      'margin-left': $width * 0.5,
      'margin-right': $width * 0.5,
    );
  } @else {
    $gutters: ($position: $width);
  }

  @return $gutters;
}
// input
$split: get-gutters(2rem, 'split');
$left: get-gutters(2rem, 'left');

// results
$split: (
  'margin-left': 1rem,
  'margin-right': 1rem,
);

$before: (
  'margin-left': 2rem,
);

Rendering CSS Maps

Chris doesn’t always get what he wants

$split: (
  'margin-left': 1rem,
  'margin-right': 1rem,
);
.split-gutters {
  @each $key, $value in $split {
    #{$key}: $value;
  }
}

Rendering CSS Maps

Chris doesn’t always get what he wants

$split: (
  'margin-left': 1rem,
  'margin-right': 1rem,
);
.split-gutters {
  @each $key, $value in $split {
    #{$key}: $value;
  }
}
.split-gutters {
  margin-left: 1rem;
  margin-right: 1rem;
}

Smart Rendering

(for multi-directional sites)

@mixin render($map, $dir) {
  $rtl: if($dir == 'rtl', true, false);
  $directions: (
    'before': if($rtl, 'margin-right', 'margin-left'),
    'after': if($rtl, 'margin-left', 'margin-right'),
  );
  
  @each $key, $value in $map {
    $key: map-get($directions, $key) or $key;

    #{$key}: $value;
  }
}

Smart Rendering

(for multi-directional sites)

$gutters: (
  'before': 2em
);
.smart-directions {
  @include render($gutters, 'rtl');
}
.smart-directions {
  margin-right: 2em;
}

Why Does Chris Care?

Sass Belief: CSS should look like CSS

// css
.split-gutters {
  margin-left: 1rem;
  margin-right: 1rem;
}
// not css
$split-gutters: (
  'margin-left': 1rem,
  'margin-right': 1rem,
);

see proposed content() function

Breakpoint Maps

$breakpoints: (
  footer-columns: 25em,
  toolbar-icons: 34em,
  form-columns: toolbar-icons,
  sidebar: 50em,
  calendar-tabs: 60em,
);

Breakpoint Function

@function break($point) {
  // Find breakpoints or other sizes
  $point: map-get($breakpoints, $point) or map-get($sizes, $point) or $point;

  // Recursive check
  $point: if(type-of($point) != 'number', break($point), $point);

  @return $point;
}

Prepositional Mixins

@mixin below($max, $prop: width) {
  @media (max-#{$prop}: #{break($max)}) {
    @content;
  }
}

@mixin above($min, $prop: width) {
  @media (min-#{$prop}: #{break($min)}) {
    @content;
  }
}

@mixin between($min, $max, $prop: width) {
  @media (min-#{$prop}: #{break($min)}) and (max-#{$prop}: #{break($max)}) {
    @content;
  }
}

Named Queries

.sidebar {
  @include below('off-canvas') {
    transform: translate3d(-101%, 0, 0);
  }
}

Removing Pairs

map-remove($map, $key)

// Before
$css-conf: (
  'hello': 'Boston',
  'speaker': 'Mia',
  'emcee': 'Claudina!',
);

// Remove a key:value pair
$fire-mia: map-remove($css-conf, 'speaker');

// After
$fire-mia: (
  'hello': 'Boston',
  'emcee': 'Claudina!',
);

Map Introspection

map-has-key($map, $key) | map-keys($map) | map-values($map)

$map-magic: (
  'hello': 'world',
  'speaker': 'Mia Suzanne',
);

$has-key: map-has-key($map-magic, 'speaker'); // true
$keys: map-keys($map-magic); // ('hello', 'speaker')
$values: map-values($map-magic); // ('world', 'Mia Suzanne')

List Functions

accept maps, as a list of pairs…

$css-conf: (
  'hello': 'Boston',
  'speaker': 'Mia Suzanne',
  'emcee': 'Claudina!',
);

$length: length($css-conf); // 3
$first: nth($css-conf, 1); // ('hello': 'Boston')
// etc...

Maps help in Pattern-Making

code should be meaningful & readable

Patterns Add Meaning

that’s how you know if it was worthwhile…