slides.oddbird.net/rws/smashing/tools/

Sass Modules & Tools

@ Smashing Online
Headshot of Hampton Catlin

@hcatlin Hampton Catlin

Original Inventor

2006… HAML for Ruby

%ul
  %li Salt
  %li Pepper

2007… Extended For CSS

ul
:margin-bottom 1em
li
:list-style-type disk

Original Sass Variables

!total_cols = 12
!col_width = 4em

Original Sass Mixins

=grid-container(!grid = !total_cols )
:margin-left auto
:margin-right auto
:overflow hidden
:width=(!grid * !col_width) + ((!grid - 1) * !gutter_width)
:max-width 99%

Original Control Structures

@if !switch
:float right
@else
:float left
Headshot of Natalie Weizenbaum

@nex3 Natalie Weizenbaum

Lead Language Designer

2010 Sass » Scss More CSSy Syntax

Design Principles

  • Strict Superset: All CSS is Valid Scss
  • Clear Differentiation Between CSS & non-CSS

CSS Superset style.css style.scss

Two-Syntax Options One Sass Language

  • Sass Syntax ➡ *.sass
  • Scss Syntax ➡ *.scss

2013 Multiple Implementations

LibSass » SassC | GoSass | Sass.js | Node Sass

2018 Ruby SassDart Sass

Dart Sass Reference Implementation

npm install sass

Dart Sass Up-To-Date Implementation

Dart Sass New Features Regularly

Along with bug-fixes

tl;dr Use Dart Sass

Organize Into Partial Files

Use the _partial.scss filename prefix

  • sass/
    • tools/
    • config/
      • _colors.scss
      • _fonts.scss
      • _sizes.scss
      • _index.scss
    • initial/
    • patterns/
    • layouts/
    • components/
    • style.scss

sass/config/_index.scss

@forward 'color';
@forward 'fonts';
@forward 'scale';

Use @forward To Combine Partials

sass/style.scss

@use 'config';

Use @use To Access Partials

@import Is A CSS Feature

@use & @forward Only in Dart Sass

Sass @import Dangerously Combines Files

Sass @import Creates Global Namespace

@use Loads Partials As Modules

A Module Includes

  • A Namespace
  • Public Members
    • Variables
    • Functions
    • Mixins
  • CSS Output

_colors.scss

$brand: rebeccapurple;
$_private: papayawhip;

@function tint($color, $amount) { ... }

@use 'colors'

// default 'colors' namespace
// $_private is not available

a {
color: colors.$brand;
background: colors.tint(maroon, 95%);
}

@use 'colors' as *

a {
color: $brand;
background: tint(maroon, 95%);
}

@use 'colors' as config

a {
color: config.$brand;
background: config.tint(maroon, 95%);
}

sass/config/_index.scss

@forward 'color';
@forward 'fonts';
@forward 'scale';

@use 'config'

buttons {
background-color: config.$brand;
font-size: config.$small;
}

sass/config/_index.scss

@forward 'color' as color-*;
@forward 'fonts' as font-*;
@forward 'scale' as size-*;

@use 'config'

buttons {
background-color: config.$color-brand;
font-size: config.$size-small;
}

Helps You Write Modular Code

Helps You Visualize Dependencies

Helps You Avoid Conflicts

Helps Sass Avoid CSS Conflicts

Helps Sass Add New Features

Built-In Sass Modules

math, color, string, list, map, selector, meta

@use 'sass:math'

nav {
width: math.percentage(1/3);
}

Sass Migrator

nav {
background: silver;
}

nav ul {
margin: 0;
padding: 0;
list-style: none;
}

nav li { display: inline-block; }
nav {
background: silver;

ul {
margin: 0;
padding: 0;
list-style: none;
}

li { display: inline-block; }
}

Useful for Scope Ownership

Useful for Managing Overrides

Especially With Media Bubbling

nav {
display: flex;
flex-direction: column;

@media (min-width: 30em) {
flex-direction: row;
}
}

Also Works With @support Queries

nav {
float: left;
width: 25%;

@supports (display: grid) {
width: auto;
}
}

& Parent Selector

a {
:link, :visited {
// a :link, a :visited 😿
}
}
a {
&:link, &:visited {
// a:link, a:visited 😻
}
}

Be Careful Breaking Selectors

.block {
// .block {}
&__element {
// .block__element {}
&--modifier {
// .block__element--modifier {}
}
}
}

Don’t Overly Sandbox

Avoid the temptation to mimic html structure

body {
nav {
ul {
li { display: inline-block; }
}
}
}
body nav ul li { display: inline-block; }

Sass is A Pre-Processor

Your Sass is only as good As The CSS It Generates

Criteria for Any CSS Tools

In The Browser There is Only CSS

Sass Variables

$variable-name: value;

Using Variables

nav {
background: $brand-color;
}

Interpolating Variables

#{$selector} {
--brand-color: #{$brand-color};
}

Fun Legacy Fact

$variable-name == $variable_name

Sass Variables Scope To Modules & Brackets

Custom Properties Inherit Through DOM Structures

Sass Variables Compile To A Single Value

Custom Properties Resolve In The Cascade

Sass Variables For Code Automation

Custom Properties For Dynamic Style

Many Tools Have Variables

Many Tools Generate CSS

Sass is Designed For CSS

Sass Values Based on CSS

Numbers & Lengths

3.14 | 34em | 15vw | &c.

Sass Strings

'nav main' | bold

Sass Colors

#df207f | hsl(...) | rgb(...) | &c…

Sass Lists

'Helvetica Neue', sans-serif | auto 1fr 30em | &c.

Additional Sass-Only Values

true | false | null | <functions>

Data Maps

$map-variable: (
keys: values,
structured: data,
);
$color-brand-blue: hsl(195, 85%, 35%);
$color-brand-orange: hsl(24, 100%, 39%);
$color-brand-pink: hsl(330, 85%, 48%);
// 'colors' module...
$brand: (
'blue': hsl(195, 85%, 35%),
'orange': hsl(24, 100%, 39%),
'pink': hsl(330, 85%, 48%),
);
@use 'colors';
@use 'sass:map';

a
{
// map.get($map, $key)
color: map.get(colors.$brand, 'blue');
}
@use 'sass:map';

$_brand: ( ... );
$_ui: ( ... )

$colors: map.merge($_brand, $_ui);

Meaningful Grouping For Humans & Machines

@if & @else Define Optional Code

@use 'sass:color';

button
{
color: $btn-color;

@if (color.lightness($btn-color) > 50%) {
background: black;
} @else {
background: white;
}
}

@each & @for Define Looping Code

@for Loops Number Ranges

@for $n from 1 through 10 {
.item:nth-child(#{$n}) {
// generate styles for nth-child 1-10
}
}

@each Loops Lists & Maps

html {
// each <key>, <value> in <map>
@each $name, $color in $brand {
--color-brand-#{$name}: #{$color};
}
}
@each $name, $color in $buttons {
[data-btn-color='#{$name}'] {
background-color: $color;
}
}

Maps can be Updated Dynamically

@use 'sass:color';
@use 'sass:map';

@each $name, $color in $colors {
$generated: (
'#{$name}-light': color.scale($color, $lightness: 50%),
'#{$name}-dark': color.scale($color, $lightness: -50%),
);

$colors: map.merge($colors, $generated);
}

Maps can be Accessed Dynamically

@mixin background($color-name) {
background: map.get($color-name);
}

// You can't interpolate variable names...
// $#{$color-name}

Sass Mixins Store Blocks of Code

Like massive variables…

@mixin button-base {
border: thin solid;
border-radius: 0.12em;
font: inherit;
padding: 0.25em 1em;
}
[data-btn='danger'] {
@include button-base;
background: maroon;
color: white;
}
@mixin button($background, $text) {
background: $background;
color: $text;
border: thin solid;
border-radius: 0.12em;
font: inherit;
padding: 0.25em 1em;
}
[data-btn='danger'] {
@include button(maroon, white);
}

Sass Functions Return a Value

@use 'sass:color';

@function contrast($color) {
@if (color.lightness($color) > 50%) {
@return black;
} @else {
@return white;
}
}
button {
background: $brand-color;
color: contrast($brand-color)
}
@mixin button($background) {
background: $background;
color: contrast($background);
border: thin solid;
border-radius: 0.12em;
font: inherit;
padding: 0.25em 1em;
}

Simpler map.get Color Function

@use 'config';
@use 'sass:map';

a
{
color: map.get(config.$colors, 'action');
}
@use 'sass:map';
$colors: ( ... );

@function color($name) {
@return map.get($colors, $name);
}
@use 'config';

a
{
color: config.color('action');
}

Create Modular Toolkits

Capture Control Structures

@use 'sass:color';
@use 'sass:map';

@each $name, $color in $colors {
$generated: (
'#{$name}-light': color.scale($color, $lightness: 50%),
'#{$name}-dark': color.scale($color, $lightness: -50%),
);

$colors: map.merge($colors, $generated);
}
@use 'sass:color';
@use 'sass:map';

@function build-palette($colors) {
@each $name, $color in $colors {
$generated: (
'#{$name}-light': color.scale($color, $lightness: 50%),
'#{$name}-dark': color.scale($color, $lightness: -50%),
);

$colors: map.merge($colors, $generated);
}
}
@use 'tools';

$colors: ( ... )
$colors: tools.build-palette($colors);

Tools Encourage Consistency

Tools Encourage Best Practice

Tools Encourage Systems

Tools Free Us To Focus on Details

My job is to make sure the system is modular and flexible enough to be used in any number of unpredictable ways.

– Mina Markham, Pantsuit

👍 Opinionated About Process

👎 Opinionated About Style

Balance of Firm And Flexible

Tools Are A ByProduct

Notice Patterns And Capture Them

Reuse Tools And Adapt

Over-Engineering Is Part of The Process

Ok, so… There’s A Map Problem

Internal Reference

$colors: (
'brand-blue': hsl(195, 85%, 35%),
'action': 'brand-blue', // just a string
);

Internal Reference

@use 'sass:map';

$colors: (
'brand-blue': hsl(195, 85%, 35%),
'action': map.get($colors, 'brand-blue');
);

ERROR Undefined variable $colors

Recursive Map-Get Function

$colors: (
'brand-pink': hsl(330, 85%, 48%),
'escher': 'brand-pink',
'godel': 'escher',
'bach': 'godel',
'kevin bacon': 'bach',
);
@use 'sass:map';

@function color($name) {
$result: map.get($colors, $name);

@if map.has-key($colors, $result) {
$result: color($result);
}

@return $result;
}

color('kevin bacon')
hsl(330, 85%, 48%)

Map Adjustments

$colors: (
'brand-pink': hsl(330, 85%, 48%),
'escher': 'brand-pink',
'godel': 'escher',
'bach': 'godel',
'kevin bacon': 'bach' ('lighten': 20%),
);

(more function magic we won’t dig into)

color('kevin bacon')
hsl(330, 85%, 68%)

¯\_(ツ)_/¯

👎 Requires More Tooling And Custom Syntax

👍 Meaningful Organization

👍 Visible Relationships

👍 Single Source Of Truth

👍 Automated Systems

colors, type, animations, sizes, etc.

Find or Build Tools That Fit You

Tools That Encourage You

Tools That Get Out of Your Way