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

Sass Modules & Tools

@ Smashing Online
Headshot of Hampton Catlin

@hcatlin Hampton Catlin

Original Inventor

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

Two-Syntax Options One Sass Language

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

2013 Multiple Implementations

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

Dart Sass New Features Regularly

Along with bug-fixes

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

sass/style.scss

@use 'config';

Sass @import Dangerously Combines Files

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

Built-In Sass Modules

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

@use 'sass:math'

nav {
width: math.percentage(1/3);
}
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; }
}
nav {
display: flex;
flex-direction: column;

@media (min-width: 30em) {
flex-direction: row;
}
}
nav {
float: left;
width: 25%;

@supports (display: grid) {
width: auto;
}
}
a {
:link, :visited {
// a :link, a :visited 😿
}
}
a {
&:link, &:visited {
// a:link, a:visited 😻
}
}
.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; }

Your Sass is only as good As The CSS It Generates

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

Numbers & Lengths

3.14 | 34em | 15vw | &c.

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

@use 'sass:color';

button
{
color: $btn-color;

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

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

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

Over-Engineering Is Part of The Process

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

👎 Requires More Tooling And Custom Syntax