Sass Fundamentals

Sass Fundamentals

Introduction

GitHub Repo

CSS Pitfalls

  • No encapsulation
  • No variables
  • Not composable
  • Bad modularity primitives
  • Globals
  • Beating-into-submission-driven-development

Preprocesser Benefits

  • Rise of the preprocessors

    • Compile to CSS
    • Parameterized(variables)
    • Composable
    • Modular
    • Plug in to your existing tools
  • Preprocessors: Why use one?

    • Add stuff that should have been there
    • Style is faster to build & easier to maintain
    • D.R.Y
    • It’s easier to keep your styles organized
    • Easy to set up
    • Toolkits on top of preprocessors

Course Agenda

  • Sass Origins & Basics
  • Variables & Mixins
  • Control flow
  • CSS Architecture
  • Defining your own Sass functions

Sass Basics

Syntax, Nesting & Selectors

  • Syntax

  • style.css

1
2
3
4
.foo {
color: green;
font-size: 1.3rem;
}
  • style.sass
1
2
3
.foo
color: green
font-size: 1.3rem
  • Terminology

    • selector
      • .foo
    • declaration block
      • {}
    • declaration
      • color: green;
    • property
      • color
    • value
      • green
  • Nesting & Scoping

1
2
3
4
<div class="container">
<div class="sidebar">Sidebar</div>
<div class="main">Main Content</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.container {
max-width: 600px;
width: 100%;
margin: auto;
background: #eee;
}

.container .sidebar,
.container .main {
padding: 10px;
}

.container .main {
margin-left: 220px;
min-height: 100vh;
border-left: 2px solid #333;
}

.container .sidebar {
width: 200px;
float: left;
}
  • Nesting & Scoping

    • Styles can be placed in the declaraton block of a parent element
    • Everything is expanded as you would expect during the Sass compilation process
    • This is already D.R.Y.’ed up quite a bit!
  • style.scss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.container {
max-width: 600px;
width: 100%;
margin: auto;
background: #eee;

.sidebar,
.main {
padding: 10px;
}

.main {
margin-left: 220px;
min-height: 100vh;
border-left: 2px solid #333;
}

.sidebar {
width: 200px;
float: left;
}
}
  • Descentant

  • html

1
2
3
4
5
6
<div class="container">
<div class="left-area">...</div>
<div class="other-thing">
<div class="left-area"></div>
</div>
</div>
  • Sass
1
2
3
4
5
.container {
.left-area {
/*...*/
}
}
  • CSS
1
2
3
.container .left-area {
/* ... */
}
  • Direct Descentant(>)
1
2
3
4
5
.container {
> .left-area {

}
}
1
2
3
.container > .left-area {

}
  • Parent Selector(&)
    • The parent selector is ofen useful in situations where adding a secondary class changes styles
1
<div class="container right-nav"></div>
1
2
3
4
5
.container {
&.right-nav {
color: #333;
}
}
1
2
3
.container.right-nav {
color: #333;
}

Challenge: Nesting

git clone git@github.com:mike-works/sass-fundamentals.git

cd sass-fundamentals

npm i

./run -e nesting

  • All buttons must have 10px padding on left and right, 2px on top and bottom
  • All buttons should have a 1px #c46 solid border, with a 2px radius
  • Adding the .btn-primary class to a button should turn its text white and background #c46
  • Adding the .btn-secondary to a button should turn its background #edbcc8 and text black
  • Disabling a button should cause its opacity to be set to 0.5

Challenge: Parent

  • Expand upon our sidebar example, so that adding the .right-nav class to the .container div can be used to toggle between left and right sidenav alignment
  • Ensure that your code passes the tests that are in place

./run -e parent

Sass Variables

Sass @import and Variables

  • Importing

    • Using an @import in CSS results in a new round-trip HTTP Request, this is a pref concern
    • The files our users download vs. files we use to manage source code are different concerns
    • JS Modules have taught us that modularity is a wonderful tool
    • @import in Sass does something else(something more useful)
  • Variables

1
2
3
4
5
6
7
8
$error_color: #f00 !default;

.alert-error {
$text_color: #ddd;
background-color: $error_color;
color: $text_color;
text-shadow: 0 0 2px darken($text_color, 40%);
}
  • Global variable declaration

    • $error_color
  • Value

    • #f00
  • Unless set else where

    • !default
  • Local variable declaration

    • $text_color
  • Variables

    • Simple arithmetic for values is ok
    • Sass can convert units, as long as dimension is the same
    • Any CSS value should be OK
  • Comments

1
2
3
4
5
6
/**
* will remain
*/
.foo {
color: red; // will be removed
}
1
2
3
/**
* will remain
*/
  • String Interpolation
1
2
3
/**
* Hue is #{hue(green)}
*/
1
2
3
/**
* Hue is 120deg
*/

Challenge: Imports and Variables

  • This looks like our first exercise
  • Except, there’s a _variables.scss
  • Primary button should have a $hopbush background and $venus border
  • Secondary button should have a $nebula background and $patina border

./run -e variables

Sass Mixins

Sass Mixins & Arguments

  • Mixins
    • Allow for re-use of style
    • Mental model: declaration block is merged, by way of @include
    • Best practice: separate from styles, like variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@mixin alert-text {
background-color: #f00;
color: #fff;
font-variant: small-caps;
}

.error-text {
@include alert-text;
}

.has-error::after {
@include alert-text;
display: inline-block;
content: attr(data-error)
}
  • Mixins: Arguments
    • Variables allow for parameterization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@mixin alert-text($color) {
background-color: $color;
color: #fff;
font-variant: small-caps;
}

.error-text {
@include alert-text(blue);
}

.has-error::after {
@include alert-text(red);
display: inline-block;
content: attr(data-error);
}

Challenge: Mixins

  • In your app.scss file, we’re making use of a new (currently empty) mixin
  • The goal is to define a means of defining the style of a button, given a color
  • Implement the mixin, such that all failing tests pass

./run -e mixins

Default Argrment Values

  • Mixin - Default Argument Values
    • We can define default values for arguments
    • Arguments can be provided in order, or by name
    • When “keyword args” used, order is ignored
1
2
3
4
5
6
7
8
9
10
11
12
13
@mixin alert-text($color: #333) {
background-color: $color;
color: #fff;
font-variant: small-caps;
}

h1 {
@include alert-text(blue);
}

h2 {
@include alert-text($color: green);
}
  • Maxins - Null Values
1
2
3
4
5
6
7
8
9
10
11
12
@mixin foo($opacity: null) {
color: #333;
opacity: $opacity;
}

.btn {
@include foo();
}

.other-btn {
@include foo(0.5);
}
  • compile to css
1
2
3
4
5
6
7
8
.btn {
color: #333;
}

.other-btn {
color: #333;
opacity: 0.5;
}
  • Mixins - Passing a Declaration Block
1
2
3
4
5
6
7
8
9
10
11
12
@mixin foo($color) {
color: $color;
.inner {
@content
}
}

.btn {
@include foo(#c69) {
color: #f00;
}
}
  • compile to css
1
2
3
4
5
6
7
.btn {
color: #c69;
}

.btn .inner {
color: #f00;
}

Challenge: range

  • The range input can be styled, but it involves lots of ugly vendor-prefixing
  • This excellent tutorial from CSS tricks describes what needs to be done in CSS. Do it the Sass way!

./run -e range

Sass Functions

Introducing Sass Functions

Built-In Functions

  • Color Functions
    • document
    • adjust-hue($color, $degrees);
    • Rotates the hue of a color by a certain number of degrees
1
2
3
.foo {
color: adjust-hue(#63f, 60deg);
}
  • Color Functions
    • darken($color, $percent);
    • lighten($color, $percent);
    • Change the brightness of a color by a certain amount
1
2
3
.foo {
color: darken(#63f, 20%); // #30c
}
  • Color Functions
    • desaturate($color, $percent);
    • saturate($color, $percent);
    • Change the saturation of a color by a specified amount
1
2
3
.foo {
color: desaturate(#f00, 75%); // #9f6060
}

Challenge: Functions

  • We wish to apply some basic “theming” to a set of buttons
1
<div class="theme-1">"""< Will be themed ""-!<div>
  • Theme mixin should take a $color as first argument, $huerot as second, and a $darkenpct as third
    • all buttons
      • border 20% darker color than background
      • hover background: 20% more saturated then 10% lightened
    • non-primary buttons
      • darkened by $darkenpct

./run -e functions

Control Flow

@if Sass Directive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@mixin foo($size) {
font-size: $size;
@if $size > 20 {
line-height: $size;
}
}

.small {
@include foo(14px);
}

.large {
@include foo(24px);
}
  • compile to css
1
2
3
4
5
6
7
.small {
font-size: 14px
}
.large {
font-size: 24px;
line-height: 24px;
}

Challenge: Control Flow

  • Only use white button text if the brightness value of a button background color is less than 70%

./run -e if

Data Structures

  • Control Flow - @for
1
2
3
4
5
@for $i from 1 through 5 {
h#{$i} {
font-size: 5rem - $i * 0.75rem;
}
}
  • compile to css
1
2
3
4
5
h1 { font-size: 4.25rem; }
h2 { font-size: 3.5rem; }
h3 { font-size: 2.75rem; }
h4 { font-size: 2rem; }
h5 { font-size: 1.25rem; }
  • Data Structures - Lists
1
2
3
4
5
6
7
8
9
$mylist: 0 0 2px #000;

.foo {
/*
shadow blur radius:
#{nth($mylist, 3)}
*/
box-shadow: $mylist;
}
1
2
3
4
5
6
7
.foo {
/*
shadow blur radius:
2px
*/
box-shadow: 0 0 2px #000;
}
  • Data Structures - Lists and @each
1
2
3
4
5
6
7
$mylist: 0 0 2px #000;

.foo {
@each $i in $mylist {
/* #{$i} */
}
}
1
2
3
4
5
6
.foo {
/* 0 */
/* 0 */
/* 2px */
/* #000 */
}
  • Data Structures - nth
1
2
3
4
5
6
7
$gradients:
(to left top, blue, red),
(to left top, blue, yellow);

.foo {
background: linear-gradient(nth($gradients, 2));
}
  • Data Structures - Maps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$mymap: (
dark: #009,
light: #66f
);

@mixin theme-button($key) {
/* Theme: #{$key} */
color: map-get($mymap, $key);
}

.btn-dark {
@include theme-button('dark');
}

.btn-light {
@include theme-button('light');
}
1
2
3
4
5
6
7
8
9
.btn-dark {
/* Theme: dark */
color: #009;
}

.btn-light {
/* Theme: light */
color: #66f;
}

Challenge: Nudging Classes

  • We’re going to build a bunch of tiny css rules, each with exactly one style declaration
  • Using @for, @each and Sass data structures, build a set of styles in 5px increments, for both margin and padding, in each of the four directions(top, bottom, left and right)
  • BONUS: Try to use 15 lines of Sass or less!

./run -e tiny

CSS Architecture

BEM Introduction

  • Block - standalone entity, meaningful on its own

    • header, container, menu, checkbox, input, button
  • Elements - A part of a block that has no standalone meaning, and is semantically tied to its block

    • menu-item, list-item, checkbox-caption, header-title
  • Modifier - A flag on a block or element, used to change appearance and/or behavior

    • disabled, highlighted, checked, size-big, color-yellow
1
2
3
4
5
6
7
8
9
<div class="textfield">
<label for="first-name" class="textfield__label">
First Name
</label>
<input name="first-name" type="email" class="textfield__input" />
<span class="textfield__validation-error">
Must be two characters or longer
</span>
</div>
1
2
3
4
5
.textfield {
&__input {}
&__label {}
&__validation-error {}
}
1
2
<div class="textfield textfield-state-validated">
</div>
1
2
3
4
.textfield {
&.textfield-state-validated {}
&.textfield-state-error {}
}

Exercise: BEM Buttons

  • Build a “one-click checkout” button using BEM
  • Basic color requirements for buttons, same as exercise 1
  • Price should have bg of #080, but #aaa when button is disabled

./run -e bem

Reusing Style with Mixins

  • Style reuse via Mixins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@mixin danger {
background: red;
color: white;
}

.btn-danger {
@include danger();
padding: 2px;
}

.alert-danger {
@include danger();
width: 100%;
}
1
2
3
4
5
6
7
8
9
10
11
.btn-danger {
background: red;
color: white;
padding: 2px;
}

.alert-danger {
background: red;
color: white;
width: 100%;
}
  • @extend
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.danger {
background: red;
color: white;
}

.btn-danger {
@extend .danger;
padding: 2px;
}

.alert-danger {
@extend .danger;
width: 100%;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.danger,
.btn-danger,
.alert-danger {
background: red;
color: white;
}

.btn-danger {
padding: 2px;
}

.alert-danger {
width: 100%;
}
  • @extend - Placeholders
1
2
3
4
5
6
7
8
9
10
11
12
13
14
%danger {
background: red;
color: white;
}

.btn-danger {
@extend %danger;
padding: 2px;
}

.alert-danger {
@extend %danger;
width: 100%;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
.danger,
.alert-danger {
background: red;
color: white;
}

.btn-danger {
padding: 2px;
}

.alert-danger {
width: 100%;
}

Challenge: @extend

  • Achieve the same outcome as exercise 1, except…
  • Do not change the appearance of the buttons at the bottom
  • Use @extend and a %placeholder

./run -e extend

Working with Sass Functions

Writing Sass Functions

  • Defining your own functions
1
2
3
4
5
6
7
8
9
10
11
12
$w: 2px;
@function double($x) {
@return 2 * $x;
}

.thin-border {
border-width: $w;
}

.thick-border {
border-width: double($w);
}
1
2
3
4
5
6
.thin-border {
border-width: 2px;
}
.thick-border {
border-width: 4px;
}
1
2
3
4
5
6
7
8
9
10
11
12
@function biggest_channel_only($color) {
$r: red($color);
$g: green($color);
$b: blue($color);
@if $r > $g and $r > $b {
@return rgb($r, 0, 0);
} @else if $g > $r and $g > $r {
@return rgb(0, $g, 0);
} @else {
@return rgb(0, 0, $b);
}
}

Challenge: Relative Luminance in Sass

  • Exercise 6 involved using @if to ensure that the text/background color combination on a button was readable
  • This quite the right concept. What we really want is to base the label text color on a difference in “relative luminance” between the label and background colors
  • First, we’ll need to convert our RGB values from their current form (a gamma-compressed color space) to a linear color space.
  • A pow($base, $exp) function has been provided
  • Next, we’ll want to weigh these linear RGB values as follows
    • Y = 0.2126R + 0.7152G + 0.0722B
  • Finally, we’ll want to change the @if involved with determining button text color, so that instead of the existing comparison, it sets a white text color when the background color and the color white differ in luminance by more than 0.7

./run -e luminance