ruk·si

💅 SASS
Basics

Updated at 2015-07-25 15:51

This guide contains basic syntax and tips how to write SASS markup.

SASS is preprocessor for CSS meaning that SASS compiles to CSS. It handles minification, removes comments from production, provides mixins so you do not have to remember all browsers specific syntax e.g. -moz-transform.

You should also check out CSS guide and HTML guide as they are related to this guide.

Architecture

SASS has two syntax variants: .scss and .sass. There are multiple syntax differences but in the nutshell, SCSS is more closer to normal CSS so any valid CSS is valid SCSS whereas SASS is more compact. Most of the examples in this note use SCSS because I personally think it's better.

// example.scss
// Blocks are made using braces.
// Mixins are defined with @mixin and used with @include
// Semicolons are required.

@mixin box-sizing($x) {
    border: 1px solid #ccc;
    padding: 20px;
}
.content {
    @include box-sizing($variable);
}
// example.sass
// Blocks are made using whitespace.
// Mixins are defined with = and used with +.
// Does not require semicolons.

=box-sizing($x)
    border: 1px solid #ccc
    padding: 20px

.content
    +box-sizing($variable)

Combine files with @import. Partial file imports are made with a name starting with an underscore _. They do not get compiled to CSS files.

@import "settings";

Have consistent @import order. Suggestion: third party stuff, own variables, own mixins, own components, own sections, own shame.

// Third Party
@import "compass";

// Variables
@import "variables/colors";
@import "variables/baseline";

// Mixins
@import "mixins/css3";
@import "mixins/media";

// Components
@import "components/tabs";
@import "components/modals";

// Sections
@import "section/header";
@import "section/footer";

// Shame
@import "shame";

Use a no-css-check file. Create global variable and mixin files that do not compile into any CSS. Import them all in "no-css-check.sass" and check that file time to time that it is empty. Global variables should include stuff like colors, baseline height and z-index groups.

// no-css-check.sass
// Load all SASS that shouldn't generate any CSS.
// Variables, mixins, etc.
@import "utility/variables";
@import "utility/vendor-prefixing";

Keep your files small. Your SASS should have less than 200 lines per file and you should use import to merge them.

Use partial files. Partials files are named using underscore e.g. _my-partial.scss. These are meant to be imported, not compiled alone.

# SASS Hierarchy Example, 7-1 system.

# At the root of the project SCSS directory, you have main import file.
# Main file should only contain @import statements.
# You can have multiple main files e.g. main-admin.scss for a different theme.
./main.scss

# Shame file contains all "not yet sorted" definitions.


# Vendors contains styles for external libraries and frameworks.
./vendors/_bootstrap.scss
./vendors/_jquery-ui.scss
./vendors/_select2.scss

# Utilities contains your site wide mixins, functions and variables.
./utility/_mixins.scss
./utility/_functions.scss
./utility/_variables.scss

# Base contains styles for basic HTML elements that apply to the whole website.
# Example selectors: body, p, h1, h2, li, ul.
./base/_document.scss
./base/_normalize.scss
./base/_typography.scss
./base/_forms.scss

# Layout contains styles that are for laying out the site and positioning.
# Example selectors: .main-header, header.main, .footer, .sidebar, nav.main
./layout/_grid.scss
./layout/_header.scss
./layout/_footer.scss
./layout/_sidebar.scss
./layout/_navigation.scss

# Components contains styles for general components and widgets.
# Example selectors: .btn, .btn-large, .form-horizontal.
./components/_carousel.scss
./components/_buttons.scss
./components/_thumbnails.scss
./components/_forms.scss

# Pages contains styles for specific pages, named after the page.
./pages/_home.scss
./pages/_contact.scss
./pages/_product-summary.scss
./pages/_product-gallery.scss

# Themes contains definitions for themes. This folder is rarely required.
./themes/_general.scss
./themes/_admin.scss

Code Layout

Same rules apply as in the CSS guide. Most notably: prefer 4 space indention, no tabs, use single quotes ', include leading zeroes.

Define variables and mixins at the start of the file. If the same $variable or @mixin is used in more then one file, place it in a separate file that does no generate any CSS e.g. variables.scss.

Have consistent definition order for properties and such. Suggestion: extends, includes, mixins, regular styles, nested selectors.

// good
.weather {
    @extends %module;
    @include transition(all 0.3s ease);
    background: $weather_accent_color;

    .title {
        @include transform(rotate(90deg));
        border-bottom: 1px solid white;
    }
}

Naming

Name everything using lower dashed case. Matches with CSS property naming and SASS native functions.

// bad
p {
    $font_size: 12px;
    $lineHeight: 30px;
    font: #{$font_size}/#{$lineHeight};
}
@mixin border_radius($value) {
    -webkit-border-radius: $value;
    -moz-border-radius: $value;
    border-radius: $value;
}

// good
p {
    $font-size: 12px;
    $line-height: 30px;
    font: #{$font-size}/#{$line-height};
}
@mixin border-radius($value) {
    -webkit-border-radius: $value;
    -moz-border-radius: $value;
    border-radius: $value;
}

Consider namespacing your code when creating an open source library.

// Sassy Unicorn project namespaced with "su-"
$su-configuration: ( ... );
@function su-rainbow($unicorn) {
    // ..
}

Comments

Use simple comments //. Comments in SASS files won't show up in the compiled CSS.

// This will not show in the CSS.
p {
    color: #222222;
}

Document every function and mixin. Documenting global variables is a good practice most of the time.

/// Vertical rhythm baseline used all over the code base.
/// @type Length
$vertical-rhythm-baseline: 1.5rem;

Variables

Use variables on all related values you use more than once. !default variables are assigned only if nothing is assigned. Note that variables are global if first assignment was in global.

$base-font-color: #222 !default;

.content {
    // Compiles to .content p
    p {
        // Using variables.
        color: $base-font-color;
    }
}

You can do calculations with variables. Result unit is same as the first parameter. You cannot combine relative and absolute units.

$color: #333333;
h2 {
    font-size: 10px + 4pt; // => 15.33333px;
    color: $color + #111111;
}

Top-level numeric calculations should always be wrapped in parentheses. Improves readability and prevent some edge cases on evaluation.

// bad
.foo {
    width: 100% / 3;
}

// good
.foo {
    width: (100% / 3);
}

Add unit to a number using multiplication Most people think unit string can safely be appended to a number, which is wrong. Appending the unit will cause minor but hard to find bugs.

$value: 42;
$length: $value + px;  // bad
$length: $value * 1px; // good

Remove unit from a number using division.

$length: 42px;
$value: str-slice($length + unquote(''), 1, 2); // bad
$value: $length / 1px;                          // good

There are numerous helper functions for converting values.

$color: #333333;
h1 {
    background: rgba($color, 0.8);
}
p {
    background: rgba(#222222, 0.8);
}

Don't set a base font size as a variable. Use project wide baseline variable to specify line heights and other heights, but having a base font size is not a good idea in the end.

// bad
$font_size: 18px;
p {
    font-size: $font_size;
    line-height: $font_size * 1.5;
    margin-bottom: $font_size * 1.5;
}

// good
$baseline: 13px;
p {
    font-size: 18px;
    line-height: $baseline * 2;
    margin-bottom: $baseline * 2;
}

Use interpolation. You can use interpolation to define property names.

$side: left;

.girder {
    border-#{$side}: 4px solid #ccc;
    h2 {
        font-size: 24px;
    }
}

.notice {
    border-#{$side}: 8px solid #797979;
    a {
        color: #222;
    }
}

Data Structures

Use lists. These lists can be looped through.

$authors: nick aimee dan drew;
// length($authors) => 4
// index($authors, nick) => 1

$authors: append($authors, tim);
// length($authors) => 5

$authors: join($authors, $authors);
// length($authors) => 10

.author {
    float: left;
    width: 100% / length($authors);
}

@each $a in $authors {
    .author-#{$a} {
        background: url(#{$a}.jpg);
    }
}

$styles: background #333, margin 20px, padding 10px;

.factory {
    @each $style in $styles {
        #{nth($style, 1)}: #{nth($style, 2)};
    }
}

Use maps.

$sectionColors: (
    'header': #111111,
    'article': #444444,
    'footer': #888888,
);

@each $section, $color in $sectionColors {
    .section-#{$section} {
        background-color: $color;
    }
}

Control Structures

Use @if-statements.

$theme: pink !default;
header {
    @if $theme == dark {
        background: #000000;
    } @else if $theme == pink {
        background: pink;
    } @else {
        background: #ffffff;
    }
}

// There also are conditional property values.
$theme: light !default;
header {
    background: if($theme == dark, #000000, #ffffff);
}

When testing against falsy values, use not.

// bad
@if index($list, $item) == null {
    // ...
}

// good
@if not index($list, $item) {
    // ...
}

Use @each-loops.

$authors: nick aimee, dan drew;
@each $author in $authors {
    .author-#{$author} {
        background: url(author-#{$author}.jpg);
    }
}

Use @for-loops.

.item {
    position: absolute;
    right: 0;
    @for $i from 1 through 3 {
        &.item-#{$i} {
            top: $i * 30px;
        }
    }
}

Use @while-loops.

.item {
    position: absolute;
    right: 0;
    $i: 1;
    @while $i < 4 {
        &.item-#{$i} {
            top: $i * 30px;
        }
        $i: $i + 1;
    }
}

Functions

Define and use functions. Functions take parameters and return values e.g. for a property.

@function fluid($target, $context)  {
    @return ($target / $context) * 100%;
}
.sidebar {
    width: fluid(350px, 1000px); // => 35%
}

Check out and use native math functions.

round($number)
ceil($number)
floor($number)
abs($number)
min($list)
max($list)
percentage($number)

// For example:
.sidebar {
    width: percentage(350px / 1000px); // => 35%
}

Check out and use native color functions.

mix($color1, $color2, $firstColorPercentageIfNot50);
scale-color($color, $saturation: -30%, $lightness: 20%, $green: 90%)
hsl($hue, $saturation, $lightness);
adjust-hue($color, $amount);
lighten($color, $percentage);
darken($color, $percentage);
saturate($color, $percentage);
desaturate($color, $percentage);
grayscale($color);
invert($color);
complement($color);
opacify($color, .5)
transparentize($color, .5)

// Get specific info about color.
red($c);
green($c);
blue($c);
saturation($c);
lightness($c);
hue($c);
alpha($color);

Selectors

Use nest selectors sparingly. & is called parent selector.

.content {
    // Compiles to .content:hover
    &:hover {
        font-weight: bold;
    }
    // Compiles to .content.error
    &.error {
        color: red;
    }
}

Never nest more than three levels. You may have deeper nesting by specifying multipart selectors. Always consider which is more readable.

// acceptable, but becoming confusing
.weather {
    .cities {
        .city {
            color: #222;
        }
    }
}
// better, has only one level of nesting, easier to read
.weather .cities .city .block .house {
    color: #222;
}

Avoid long nested blocks. Good limit is around 40 lines. Does not fit in one screen and you lose sense of context.

// bad
.weather {
    .cities {
        .city {
            // ...200 lines...
        }
    }
    .region {
        // What region is this?
        // *scroll scroll scroll*
        // Oh, weather region, k.
        // *scroll scroll scroll*
    }
}

Mixins

Define and use mixins. Mixin is a block of reusable markup that can take parameters.

@mixin button {
    border: 1px solid #cccccc;
    font-size: 1em;
    text-transform: uppercase;
}
.btn-a {
    @include button;
    background: #777777;
}
.btn-b {
    @include button;
    background: #ffff00;
}

@mixin box-sizing($value: border-box) {
    -webkit-box-sizing: $value;
    -moz-box-sizing: $value;
    box-sizing: $value;
}
.content {
    @include box-sizing;
    padding: 20px;
}
.callout {
    @include box-sizing(content-box);
}

// You can refer parameters with keynames also.
@mixin button($radius, $color: #000000) {
    border-radius: $radius;
    color: $color;
}
.btn-a {
    @include button($color: #777777, $radius: 5px);
}
.btn-a {
    @include button(4px);
}

// Variable argument
@mixin transition( $values... ) {
    -webkit-transition: $values;
    -moz-transition: $values;
    transition: $values;
}
.btn-a {
    @include transition(color 0.3s ease-in, background 0.5s ease-out);
}

// Interpolation with mixins
@mixin highlight($color, $side) {
    border-#{$side}-color: $color;
}
.btn-a {
    @include highlight(#ff0000, right);
}

All properties with vendor prefixes should use a mixin. They will change in the future.

// good
@mixin transform($value) {
    -webkit-transform: $value;
    -moz-transform: $value;
    -ms-transform: $value;
    -o-transform: $value;
    transform: $value;
}

You can group rules with @extend. Placeholder selectors are marked with %. They can be extended but don't compile to selectors of their own. But as extend can cause confusing rules when applied a lot, use extend sparingly.

// Note that IE9 has around 4000 selectors per file limit!
%btn {
    background: #777777;
    border: 1px solid #cccccc;
    font-size: 18px;
    text-transform: uppercase;
}
// These extends will create one rule ".btn-a, .btn-b"
.btn-a {
    @extend %btn;
    // Following creates ".btn-a.minimal", but no ".btn-b.minimal"
    &.minimal {
        text-transform: lowercase;
    }
}
.btn-b {
    @extend %btn;
    background: #ffff00; // Does not affect btn-a
}

You can nest media queries. The resulting CSS will be ugly as each nested media query gets own declaration. You cannot @extend anything outside the media query.

.sidebar {
    border: 1px solid #CCC;

    @media (min-width: 700px) {
        float: right;
        width: 30%;
    }
}

// @content keyword can be used to fetch mixin call content.
@mixin respond-to($value, $query) {
    @media ($value: $query) {
        @content
    }
}

.sidebar {
    border: 1px solid #CCC;

    @include respond-to(max-width, 600px) {
        float: right;
        width: 30%;
    }
}

Compass

Compass adds reusable patterns, utilities and modules on top of SASS. I personally don't like Compass after trying it in a few projects. A lot of functionality but a very few you actually need.

@import "compass"
@import "compass/layout"
@import "compass/reset"

// @include sticky-footer(72px, "#layout", "#layout_footer", "#footer");
// @include box-sizing(border-box);
// @include background(linear-gradient(top, #999, #333))

Sources