UX Design and Development course

A Sass Style Guide

With all of Sass' new found powers, the responsibility of code quality is even more paramount. Leaving code littered with unclear rule separations, randomly imported mixins and no clear use of extended placeholders will quickly decrease the readability, scaleability and maintainability of your code. This will also increase the sneering and aggravation of other developers on your team. Guaranteed.

While there are no guarantees that following a few simple best practices will make your code better, it will make it easer to read and maintain.

Nesting: don't go too deep

Earlier we discussed a core strength of Sass, this being it's ability to remove unnecessary repetition from your nested CSS selectors. Whereas CSS requires selectors to be duplicated, Sass does not.

In the following CSS example, the parent selector .block is repeated each time as the author nests additional selectors for specificity. This pattern continues to repeat itself with each additionally nested selector. The div, the ul and the li are repeated as the author finally reaches the p.

.block div { border: 1px solid black; }
.block div ul { width: 100%; float: left; }
.block div ul li { float: left; width: 100%; background: orange; }
.block div ul li p { font-weight: bold; line-height: 1.5em; }

Sass' indentation syntax allows for the author to simply indent the nested child selector without repeating it's parent. As shown in the following example, each additional indentation tells Sass to inherit the previously nested selector.

.block div
  border: 1px solid black
  ul
    width: 100%
    float: left
    li
      float: left
      width: 100%
      background: orange
      p
        font-weight: bold
        line-height: 1.5em

With SCSS, the principal is the same. Indentation is used to assist in readability of the code, but it's the semi-colons ; that are required to separate declarations. Curly-brackets { ... } are required for block separation and to designate selector nesting. Shown in the following example each newly nested selector is placed within a new set of curly-brackets { ... }.

.block {                /* .block opening bracket */
  div {                 /* div opening bracket */
    border: 1px solid black;
    ul {                /* ul opening bracket */
      width: 100%;
      float: left;
      li {              /* li opening bracket */
        float: left;
        width: 100%;
        background: orange;
        p {             /* p opening bracket */
          font-weight: bold;
          line-height: 1.5em;
        }               /* p closing bracket */
      }                 /* li closing bracket */
    }                   /* ul closing bracket */
  }                     /* div closing bracket */
}                       /* .block closing bracket */

Nesting is a powerful feature, but be mindful of the inception rule. It is easy to fall into this comfortable trap of using selector inheritance to mimic your markup structure in attempts to dominate the cascade as clearly was the argument in this 37Signals article. Succumbing to the pressures of specificity, you will find yourself in a CSS selector nightmare. Style rules will be extremely difficult to reuse and very fragile to edit.

A common rule of thumb, if you are thinking of nesting a fourth time, you should ask yourself, "Does this selector really requires this much specification?" Can the code be abstracted so it can be used more universally? In CSS, and even more-so in Sass, keeping your selectors shallow and properly extending declarations will increase portability and reusability of your code.

Let's take an opportunity to refactor the previous example. In Sass we can easily reduce the level of specificity by simply removing some of the indentation.

.block
  div
    border: 1px solid black
  ul
    width: 100%
    float: left
  li
    float: left
    width: 100%
    background: orange
  p
    font-weight: bold
    line-height: 1.5em

With SCSS we can accomplish the same, but SCSS requires you redefine your nesting by moving the curly-brackets { ... } as shown in the following example.

.block {
  div {
    border: 1px solid black;
  }
  ul {
    width: 100%;
    float: left;
  }
  li {
    float: left;
    width: 100%;
    background: orange;
  }
  p {
    font-weight: bold;
    line-height: 1.5em;
  }
}

Either Sass or SCSS, the following CSS output will be the same. The result, as shown, less nesting means shallower style rule definitions, better portability and faster code.

.block div { border: 1px solid black; }
.block div ul { width: 100%; float: left; }
.block div ul li { float: left; width: 100%; background: orange; }
.block div ul li p { font-weight: bold; line-height: 1.5em; }

Declaration listing best practices

How you write out your selectors has a direct impact on the readability of your code. Compounded with dynamic features like injecting code with mixins, this also has a direct impact on the output CSS cascade.

Much like standard CSS, I want my code to be readable, simple to follow and easy to update. I always list my parent specific declarations directly under it's selector and then list the indented child selectors to keep readability at a maximum.

See how I listed the CSS rules specific to .foo directly after declaring the selector. At the end of the parent selector's rules is when I declare it's nested, or child selector .nested-foo. Within this child selector I will continue to follow the same pattern.

.foo {
  font-size: 12px;
  padding: 10px;
  width: 50%;
  .nested-foo {
    background-color: green;
  }
}

Using mixins

Using the @mixin directive you can engineer smart and reusable code to be used throughout your application.

When using mixins within selectors, the placement of mixins has a direct impact on the cascade of your output CSS. The role of a mixin is to physically inject, or mix-in code, where referenced. When a mixin is randomly placed into a selector, depending on what is in the mixin, this could either accidentally over-write a preceding CSS rule or you may unknowingly write a CSS rule that is duplicated by the mixin.

Consistency plays a huge role in clean code. When mixins are always in a consistently expected location, this will help other authors be able to quickly scan and edit code.

In the following example notice how I included the mixin transition directly after declaring the selector. By doing so, we have a clear expectation as to the output CSS. After the included mixin is when I list the rules that are specific to this selector. Again, this pattern would follow suit with the nested .nested-foo selector.

.foo {
  @include transition(all, 0.6s, ease);
  background-color: orange;
  width: 50%;
  .nested-foo {
    width: 25%;
    margin: o auto;
  }
}

When using mixins, tt is considered best practice to only create mixins that use keyword arguments. By doing so, you aren't simply repeating code, but leveraging a pattern of attributes whose vales are being dynamically updated with each use.

Using extended selectors

If you are a follower of OOCSS, extending selectors should feel very natural to you. Much like mixins, Sass' @extends directive is a great tool for creating and managing repeated code. Unlike mixins, extends do not accept arguments and do not inject code where called. Instead, an extended selector will be modified in it's place of origin within the cascade by appending the extending selector.

In the following example I created the selector of .default_gray_border . In the following selector .promoters_box I extended .default_gray_border using the @extend directive.

// Style class object
.default_gray_border {
  @include border-radius(25px);
  border: 1px solid gray;
}
// Semantically named class
.promoters_box {
  @extend .default_gray_border;
}

You will see in the following output CSS that Sass concatenated the two selectors together as these selectors share the exact same CSS rules.

// Output CSS
.default_gray_border, .promoters_box {  // notice the chained selectors
  -webkit-border-radius: 25px;
  -moz-border-radius: 25px;
  border-radius: 25px;
  border: 1px solid gray;
}

When working with extends, I follow the same placement rules as with mixins as the consistent placement of extends will increase readability of the code. In the following example the extended selector is listed directly after the declared selector and then parent specific styled follow.

.foo {
  @extend .default-transition;
  background-color: orange;
  width: 50%;
}

Sass for styles, SCSS for logic

Leveraging the fact that .sass files can live harmoniously with .scss files, some developers have adopted a workflow of, using SCSS for all logic (mixins, extends and functions) and then using Sass for the CSS styling.

When writing style rules for your design, Sass is quick and get's the job done. Less key strokes and a quick tab in for nesting are all great things when writing code. An added benefit of Sass is the ability to quickly redefine a series of selectors and/or rules by simply changing the level of indentation, versus having to redefine nesting by moving around curly-brackets { ... }. Let's face it, if you are using Sass, writing CSS should pretty much roll off your fingertips. Writing out selectors and rules is sans logical thought process and you are simply executing output at this time.

On the other hand, when it comes to complex thought processes, the use of curly brackets { ... } has been stated to assist in a developer's ability to see the logical code groupings I have found that using the SCSS syntax when writing logical code helps me to slow down and really take a hard look at the code I am writing.

Good code habits include writing comments and Sass is no different. SCSS allows for comments to be placed inline with the line code you are referencing. Due to the whitespace specifications, this is not possible in Sass. SCSS' form of commenting allows developers to really be expressive in their notes.

Code comments

Sass supports both invisible and visible comments. Using // before any Sass, this will place a comment in your code, but will not be output in the processed CSS. Using the standard /* */ CSS comments in your Sass, when processed this will be output in your CSS.

Leaving comments or instructions in your code is just good practice. I find it essential to leave good instructions behind about my code using the invisible technique as I begin to engineer increasingly more complicated Sass.

The following example is a sample of code from the Compass library illustrating a good use of comments.

//  override to change the default
$default-background-size: 100% auto !default;
// Set the size of background images using px,
// width and height, or percentages.
// Currently supported in: Opera, Gecko, Webkit.
//
// * percentages are relative to the background-origin
// (default = padding-box)
// * mixin defaults to: `$default-background-size`
@mixin background-size(
  $size-1: $default-background-size,
  $size-2: false,
  $size-3: false,
  $size-4: false,
...a

Mixin, selector, function naming conventions

Sass' naming conventions are inherited from CSS. Lowercase, hyphen-separated names, like the following function examples, are considered standard.

@mixin text-format($size, $family, $color) {
  font: {
    size: $size;
    family: $family;
  };
  color: $color;
}

.site-header {
  @include text-format(12px, verdana, red);
}

While this may be preferred, you will see many examples out there using underscores example_name as well camel case exampleName or Pascal case ExampleName, there is no right or wrong here. As long as you are consistent in your naming convention, that's what really matters.

A word of caution. When naming mixins, but sure to always be consistent with using either dashes -, or underscores _. I am not sure if this is a bug or a feature, but when importing mixins, the dash and underscore can be used interchangeably with the same name and it will work.

In the following example I will crate a mixin and name it using a dash -. But in the selector below, I will include the mixin using an underscore _. The result will be the mixin will process into the selector without issue.

=block-mixin
  background: green
.block
  +block_mixin

The resulting CSS

.block {
  background: green;
}

Working with partials, manifests and globbing

With CSS, all of your code is contained in a single document and with each new feature this document increases in complexity while decreasing in readability and maintainability. Sadly, these same poor development practices have made their way into Sass development as well. To add insult to injury, it is not uncommon to see files with large blocks of code that include functional Sass like variables, functions, mixins as well the presentational selector specific Sass. While this will work in a pinch, it is not best practice to have all your code in one place.

Sass gives us the power to break our code into smaller, easier to manage chunks of code called partials. In this section we will discuss how to best break apart our Sass and how to stitch it back together via techniques like manifests and globbing.

Partials

Breaking code down to smaller chunks can be a difficult process if you do not have a good convention to follow. In the next chapter I will go into greater detail about how to best manage resources like variables, functions, mixins and presentational styles, but for now, let's understand that it is a better management technique to break out your mixins, functions, variables and presentational styles into different partials.

A partial is any file with an underscore _ preceding the name. When Sass sees these files, it will not process them into CSS files. A partial requires that it be imported into another file that will inevitability be processed into CSS in order for it to be output.

In the following example, you will see a simple Sass architecture that illustrates this principal. Notice how application.sass is the only file that does not contain an underscore _ in the name as this will be the file that is output to CSS.

stylesheets/
|-- application.sass    // Sass manifest file
|
|-- _reset.sass         // Partials
|-- _variables.scss             |
|-- _functions.scss             |
|-- _mixins.scss                |
|-- _base.sass                  |
|-- _layout.sass                |
|-- _module.sass                |
|-- _state.sass                 |
|-- _theme.sass         // Partials

You are not limited by the number of CSS files you need to output. This strategy could be used for the creation of browser or device specific styles as well. As illustrated in the following example, I have added more files for browsers like Internet Explorer and devices like mobile and tablets that I intend to output CSS.

stylesheets/
|-- application.sass        // Core manifest file
|-- IE8.sass                // Browser manifest file
|-- mobile.sass             // Device manifest file
|-- tablet.sass             // Device manifest file
|
|-- _reset.sass             // Partials
|-- _variables.scss                 |
...
|-- _module.sass                    |
|-- _state.sass                     |
|-- _theme.sass             // Partials

Manifests

When using a Sass file architecture made up mostly of partials, except for the ones that we intend to output as CSS, these output files will be your Sass manifest file(s). A manifest will manage all of the Sass partials to be imported, as well import any Compass extensions or additional Sass code libraries in your project.

Unlike standard CSS, Sass' @import directive is part of the preprocess compile of your code. This does not require additional HTTP requests when sent to the client as all your CSS will be compiled, and minified if desired, into a single document. As your project scales, you are encouraged to break your files into smaller manageable chunks of code and reassemble via a manifest.

In the following example see how I use the @import directive to load all the partials into a single document and output as CSS. Another thing to note is the absence of a file type extension. Sass or SCSS, just like Honey Badger, @import doesn't care.

@import "variables";
@import "functions";
@import "mixins";
@import "reset";
@import "base";
@import "layout";
@import "module";
@import "state";
@import "theme";

The order in which you list your imports is the order that Sass will follow when processing your code. If the selector you wrote requires a mixin to be loaded before it is used, be sure to list the file containing the mixin prior to the file that uses the mixin.

It is common place in architectures like this that all logical Sass are imported first. In the previous example you can see that I loaded all the site's global variables, then functions followed by mixins. Once these files are loaded into memory, the following Sass files, who's functions are to create CSS, will be able to take advantage of the logical Sass code. This is a pattern we will want to repeat as we get deeper into a more complex file architecture.

Take note, loading logical code into memory for remaining Sass files to take advantage of, only works with imported partials. Remember that any file without a preceding underscore will be processed and output into a CSS file. If this requires any logic to be present in order to process rules, those files must be imported first.

In the following example I will illustrate how a stand alone CSS file needs to import Sass logic before it can process the CSS rules contained within.

 @import "variables";
@import "functions";
@import "mixins";

.foo {
  background-color: $default-color;
  ...
}
...

When loading additional Sass code libraries such as Compass, in order to take advantage of the library's power, you are required to load these libraries first.

On the other hand, when loading CSS from plug-in apps, is most likely that you will want to load these last as not conflict with custom selectors you have written.

As well, if these plug-in styles require customization, importing them last will allow you to take full advantage of any libraries you have imported and custom code you have written.

The following example notice how I imported Compass first so that my project specific code can take full advantage of the Compass library. At the end of the manifest I then import my plug-in flipclock library.

// Included libraries
@import "compass/css3";

// Project specific code
@import "reset";
@import "variables";
@import "functions";
@import "mixins";
@import "base";
@import "layout";
@import "module";
@import "state";
@import "theme";

// Imported plug-in libraries
@import "flipclock";

While using a Sass manifest file is a great solution, there are some additional patterns we can use in order to keep this file from becoming a giant dumping ground.

A pattern I leverage is the use of additional manifests within sub-directories. Let's say for example that you begin to create a large resource of mixins in your project. As this file grows in size, it becomes increasingly harder to mentally parse. The suggested pattern is to break this file into smaller, more digestible chunks of code and place them into a directory. Using a manifest file within that directory, you import a single reference into your application manifest and add new imports to your more specific manifest.

The following example illustrates an updated file structure with a mixins directory containing a Sass manifest file. Notice the _manifest file contained within the mixins sub-directory.

stylesheets/
|-- application.sass        // Sass manifest file
|
|-- _reset.sass             // Partials
|-- _variables.scss                 |
|-- _functions.scss                 |
|-- _base.sass                      |
|-- _layout.sass                    |
|-- _module.sass                    |
|-- _state.sass                     |
|-- _theme.sass             // Partials
|
|-- _mixins/                // Directory
|  |-- _manifest.scss
|  |-- _grid_calc.scss
|  |-- _arrow_tooltip.scss

This update requires a very simple update to my site manifest file. In the following example you will see that I updated from a simple reference to a mixin Sass file, mixins.scss, to a manifest file contained within a sub-directory, mixins/manifest.

The naming of this file _manifest.scss is purely convention. Feel free to name this file anything you like, as long as it makes sense to you and your team.

// Included libraries
@import "compass/css3";

// Project specific code
@import "reset";
@import "variables";
@import "functions";
@import "mixins/manifest";  // import sub-directory manifest
@import "base";
@import "layout";
@import "module";
@import "state";
@import "theme";

// Imported vender libraries
@import "flipclock";

Globbing

Another technique available, although not native to Sass, is file globbing. Globbing refers to pattern matching based on wildcard characters that allows Sass to assemble all the partials within a directory without a specific manifest file. Whereas I stated earlier, the order in which files are imported and processed is dictated by the order in which they are listed, this is not the case with globbing. Without a specific list to go by, Sass will assemble the files in alphabetical order.

Globbing is not the answer to all importing cases. For example, if you have a sub-directory for a UI pattern or module, it is common place to see files like _variables.sass, _mixins.sass and _module.sass within the directory. By order of the alphabet, the _variables.sass file will be loaded last. This will break the Sass processor as it is likely that the mixin or module will require a value for a variable listed in _variables.sass. In these cases I am left with coming up with a naming convention to ensure the appropriate alphabetical order. That is a really bad idea.

I strongly recommend that in the cases where a specific order of importing is required, globbing is not the answer and make use of the sub-directory manifest pattern.

On the other hand, if you have a library of code where it doesn't matter at all what the order of import is, then this is a great solution. For example, a sub-directory of animation mixins is a great use for globbing. A directory of functions, again, a great use for globbing.

Globbing is used by a lot of developers. If you are a Rails developer, this feature is made available to you via the sass-rails Gem. If you are not using Rails, Chris Eppstein has made this feature available to all users via a plug-in Gem.

In the following example I will illustrate how globbing allows me to do away with sub-directory partials. See how all the files contained within mixins are imported via the wildcard /* expression.

...
@import "functions";
@import "mixins/*";   // import sub-directory manifest
@import "base";
...

The wildcard expression / is optimal if there are no sub-directories contained within the directory you are globbing. In the situation I am using additional sub-directories, the expression of /**/ is required.

...
@import "functions";
@import "mixins/**/*";   // import sub-directory manifest
@import "base";
...

Keeping your code modular and managing an easy-to-follow manifest file will reap great rewards as your project scales. This process also will assist you in the future as you begin to engineer smarter and smarter code that you would like to reuse between projects.