Stylesheets (Sass)

Introduction

Sass is used as a stylesheet preprocessor. Vendor prefixing happens automatically via Autoprefixer. Third party CSS stylesheets (like normalize.css) that are included via the includePaths option off sass, see ./gulpfile.js/config.js

Sass files are located in the ./src/sass.

An in depth explanation of Sass and its features is beyond the scope of this documentation. It assumes that you are reasonably familiar with CSS and Sass. If this is not the case, it is recommended to get familiarized with both CSS and Sass before reading further. You can head over to the official Sass page to learn how to use it.

The Sass files usually have comments that indicate their purpose or explain the contained functionality, to get a better understanding of the codebase have a look at the source files.

NOTE: The major part of the style sheets in the unibz code base are written in the indented or .sass syntax. There are some exceptions that are documented below. Code examples will mainly use the indented syntax.

A note on Atomic Design The unibz design is based on Atomic Design principles. At its core Atomic Design means that small reusable parts are used to build more complex (reusable) structures, therefore creating a cohesive visual system by always reusing the same elements. The parts, or components, that make up a Atomic Design system are, from small to large: Atoms, Molecules and Organisms.

Structure

main.sass file

Main stylesheet file that will be compiled to main.css. It only imports partials and never has any styles of its own.

editor.sass files

Will be compiled to editor.css. This file is automatically loaded in all SilverStripe CMS WYSIWYG editors (and only there). The purpose of this stylesheet is to helps authors visualizes how text will look on the public page while editing in the CMS.

lib folder

Contains the main _vars.sass file and all mixins and functions that are used in the project. Look into the source files for further information.

base folder

Contains all base styles: raw HTML elements, fonts, breakpoint helper and generated sprite styles (learn more about sprites here).

thirdparty folder

Contains styles that are meant to style third-party components, mainly javascript widgets that have their own (un-changeble) class name structure.

layout folder

Contains the grid styles and all generic layout helper classes. Layout classes are name-spaced with .l- and grid classes are name-spaced with .g-.

ss folder

Contains styles specific to the SilverStripe CMS, mainly form styles as their class names and structure is given by the system.

modules folder

Contains the bulk of the Sass code. Each module file contains a set of reusable styles that achieve the particular appearance of a set of HTML elements (UI piece). Modules are not name-spaced.

Module class names are (loosely) based on the BEM naming convention. The concepts of BEM are very valuable to create a maintainable system. See below for a in depth explanation and the implementation.

utility folder

Contains single purpose helper classes that usually do only one thing. All utility classes are name-spaced with .u-. they usually follow this pattern:

.u-<shortcode for css property>-<value for propperty>[@<breakpoint>]

Examples:

.u-d-none // Hide element applying display: none
.u-d-ibl@sm // Apply display: inline-block to breakpoint "sm" and above.

Some of the most used helpers are dimensions-helpers, which are classes that add padding, margin and negative margin to elements:

.u-push-top-half@sm // Margin top half a spacing unit at breakpoint "sm" and above

Learn more about the $space variable.

Note: In the case of utility classes it is completely OK to use !important in a declaration, as the styles applied by utility classes should always win.

Coding style & Stylesheet authoring rules

There is a linter in place that should help keep a consistent coding style. Nevertheless the linter is not capable of correcting all errors. To aid maintainability it is important to try and keep the style consistent. The goal is to make the code look as if the same person had written it.

Indentation

Use 2 spaces for indentation.

Syntax

All stylesheets should use the Sass indented syntax. The exceptions are:

  • lib/_vars.scss
  • base/_sprite.scss

These files contain variables in the form of maps, due to a limitation of the indented syntax, maps can not be written in multi-line form. To aid the legibility of the maps these files use the .scss syntax.

Comments

Comments should use // instead of /* ... */. This way lines can be shifted around easily.

When starting a File use a double header:

//============================================
// My file
//============================================

After the header you can give an explanation of what the purpose of the file is, explain its usage or list dependencies that the file might have. Try to keep the line length of longer comments to no more than 60 characters. Prefer shorter multi line to long single line or orphaned words.

BAD

// This is some text that explains what this file is about. It shows a long line for one thing.

// This is also bad, I get the line to 60 characters and just create
// an orphan.

GOOD

// This is some text that explains what this file
// is about. It shows short multi line text.
// The length of the lines is more or less equal.

Exceptions

One exception are URLs that link to explanations which help the further understanding of the code.

Separate the double header and the explanation with a empty line. After the explanation leave two empty lines. Should the file have no explanation, then leave two empty lines between the file header and the following code.

Continue dividing the code with single headers in logical blocks. Create two empty lines before each single header.

// Wrap
//============================================

.myClass


// Links
//============================================

.myClass_links

If the code needs further division just use single line comments to divide the code. The Comment should be separated by a empty line from the preceding code and have no empty line between itself and the code it comment’s.

// Wrap
//============================================

.myClass

// Variation comment
.myClass-variation


// Links
//============================================

.myClass_links

Commenting complicated code

When authoring Sass/CSS code there is often solutions that are quite complicated or not straight forward to understand at a glance. Such solutions should be documented in depth without compromising code legibility. For this purpose use numbers to map comments to specific declarations in the code.

// Wrap
//============================================

// 1. Explain something about the class name or
//    a dependency the class might have.
// 2. Explain the use of this declaration.
// 3. Explain further.

.myClass // 1.
  visibility: hidden // 2.
  opacity: 0 // 3.

@TODO comments

Sometimes when code is left in an unperfect state it is useful to keep a to-do comment around. To be able to find them later it is important to keep the nomenclature consistent. These comments can be anywhere in the code, try to make them work with the rest of the comment style. It is also important to delete these comments once things are fixed of have changed!!

// @TODO short note on what should be done.

Full example

//============================================
// Button
//============================================

// All purpose button. Also extended by SilverStripe
// `.action` class for form buttons (<input type="submit">).

// @TODO consider using a mixin for buttons styles.


// Base
//============================================

// 1. Normalize display across browsers.
// 2. Reset `<button>` & `<input type="submit">`
//    @TODO figure out if this is enough.

.btn
  display: inline-block // 1.
  width: auto
  border: 0 // 2.


// Rounded
//============================================

.btn-rounded
  border-radius: 50%


// Icon
//============================================

.btn-icon
  position: relative

  // Icon
  &:after
    +icon("i-icon-name")

Only use classes

When using IDs for styling, the styles by nature are not reusable. This should be avoided. Classes are the natural styling hooks in the HTML & CSS world so attribute selectors should also be avoided.

Class names also should never be scoped to elements (e.g. ul.list). If for some reason a set of <div> elements needs to look like a .list, the style ul.list would not work on it, and therefore it makes the class less flexible, reusable and predictable.

// BAD
#MyId
  color: $black

// BAD
[data-some-data="test"]
  color: $gray

// BAD
ul.list

// OK
input
  &[type="submit"]

// Good
.myClass
  color: $primary

Exceptions

  • CMS generated markup: The Markup for forms generated by SilverStripe might require the styling of IDs in very isolated cases. Their reason for their use should be commented in the CSS code.
  • Third party libraries: Markup generated by Javascript libraries sometimes only provides attribute selectors as hooks for styling certain states.
  • Styling default elements: Some default elements like <input> need to be styled using attribute selectors: <input type="submit">
  • Conditional styling: Print styles for <a> elements use attribute selectors to check if the content of the href attribute is not empty or containes a # (for Javascript functionality) and appends the content of the href attribute so it is visible in the printed version. Is technique is to used sparsely and always scoped to an element.
  • Hover States: When creating a module it is possible that the HTML element is a link or not. To check for that case and only apply the hover state in case the element is a link, the use of element scoping is OK.
.myElement
  color: red

  // Apply hover style only if the HTML element is a link.
  a.&:hover
    color: green

Avoid nesting of classes (whenever possible)

When using styles like this:

.box-white
  .title-widget
    .title
      a:hover
        background: url('../img/sys/c-title-widget-title-arrow-hvr.png') right center no-repeat
        color: $color-vector

Generated CSS:

.box-white .title-widget .title a:hover {
  background: url('../img/sys/c-title-widget-title-arrow-hvr.png') right center no-repeat;
  color: #333;
}

Required HTML

<div class="box-white">
  <div class="title-widget">
    <div class="title">
      <a href="#">Link</a>
    </div>
  </div>
</div>

The conditions that have to be true to apply the style are very specific and easy to break. It makes developers fear removing a class or making any other changes (possibly improvements to the code) and induces a “I will just write this new and never re-factor the old” practice.

On the contrary:
.titleWidget
  ...

.titleWidget_link
  ...

  &:hover
    ...

Required HTML

<a href="#" class="titleWidget_link">Link</a>

Knowing that a class only depends on it’s self gives the developer the confidence to change/delete any selectors surrounding it without the fear of breaking a complicated chain and therefore inducing visual regression.

All in all nesting should be kept to a minimum as it increases specificity and makes the code less maintainable. Accepted uses are:

Exceptions

Of course there are some exceptions, the biggest one being the styling of links. The unibz design calls for links to be using borders instead of text-decoration: underline, to achieve this <span> and <a> elements are used inside block elements, to style them some nesting is necessary.

Naming conventions

Name-spaces

A name-space in CSS is a single letter at the beginning of a class name who’s purpose it is to group a given set of functionality together. And communicate it’s purpose, or at least give a hint, to a developer looking at the HTML code where it is used.

// Structure
<name-space>-<name>-[<function>]

// Examples
.l-container

.u-c-primary

The existing name-spaces in the unibz code base are:

Note: There is also a another name-space: .js-. Learn more about it here.

Module Names

As explained above modules are not name-spaced and are loosely based on the BEM naming convention. BEM introduces the notion of:

Block Element Modifier

Module class names should be written in camelCase and the filename should be equivalent to the block name.

Example:

// Block (or parent)
.dropDown
  position: relative

// Element (or child), separated by _
.dropDown_list
  position: absolute
  top: 0
  left: 0

// Modifier, separated by -
.dropDown_list-locale
  background: red

// Other element
.dropDown_item
  padding: 0.5em 0

HTML

<div class="dropDown">
  <ul class="dropDown_list">
    <li class="dropDown_item">
      Some Text
    </li>
  </ul>

  <!-- This one is red -->
  <ul class="dropDown_list dropDown_list-locale">
    <li class="dropDown_item">
      Italiano
    </li>
    <li class="dropDown_item">
      Deutsch
    </li>
  </ul>
</div>

Why is this better?

Elements are interchangeable:

<div class="dropDown">
  <ol class="dropDown_list"> <!-- Still works with ol -->
    <li class="dropDown_item">
      Some Text
    </li>
  </ol>
</div>

It creates a parent-child relationship that can be spotted at a glance:

<!-- These belong together -->
<div class="dropDown">
  <ol class="dropDown_list">
    <li class="dropDown_item">

      <!-- And these belong together -->
      <span class="label">
        <i class="label_icon"></i>
      </span>

    </li>
  </ol>
</div>

Note: Children do not have to represent the HTML structure one to one. Therefore only one underscore should be used to separate elements.

It has low specificity:

<div class="myWidget">
  <!-- Extending the dropdown component -->
  <div class="dropDown myWidget_dropdown">
    ...
  </div>
</div>
.myWidget

.myWidget_dropdown
  // Extends .c-dropDown
  // these styles overwrite the component, no problem.

Classes are easy to remove:

// Element
.dropDown_list
  position: absolute
  top: 0
  left: 0

// Remove these lines when not needed anymore
// Nothing else breaks. (Or at least is less likely to)
.dropDown_list-locale
  background: red

Finding the right name for a module

A module class name (or block -> BEM), as opposed to a name-spaced class, is always a unique term that describes it’s appearance, or structure type, and not the content that it will show. Keeping modules content agnostic allows the developer to tackle changing content requirements over time while keeping the code base clean (and keeping developer sanity).

For Example if a project on the first iteration just has a list of news:

// Module to represent news articles.
.newsList

The HTML implementation could look like this:

<ul class="newsList">
  <% loop $News %>
    <li class="newsList_item">...</li>
  <% end_loop %>
</ul>

In on the second iteration, the requirements change and there is also a list of events that should have the same appearance as news, reusing the .newsList class would lead to some confusing code:

<ul class="newsList">
  <% loop $Events %>
    <li class="newsList_item">...</li>
  <% end_loop %>
</ul>

Using a more generic name like .itemList will make the code clearer to understand at a glance and give a hint on the appearance of the content.

<ul class="itemList">
  <% loop $Events %>
    <li class="itemList_item">...</li>
  <% end_loop %>
</ul>

Of course:

There are only two hard things in Computer Science: cache invalidation and naming things.
Phil Karlton

This is why some module names are more generic than others. Try to apply the principles learned above to name things the best way you can, and do not be afraid to change a name if a better one comes to your mind (being careful to change all instances of it of course).

How do I create variations of a module? (or when is it OK to nest classes)

Often a module needs to have different variations/versions. As seen above usually this is done using a modifier class. There are to kinds of modifiers:

  • Block modifiers: .badge-light
  • Element modifiers: .badge_inner-large

In both cases it is possible that one of the modifiers needs to affect its children. In that case it is OK to nest classes:

.c-badge
  ...

  // Light version
  &-light

.c-badge_inner
  ...

  // Light version
  .c-badge-light &
  ...

Try to keep this to a minimum. and to make the selector chains as short as possible, ideally only two selectors:

/* Generated CSS */
.badge-light .badge_inner {
  ...
}
When to use a block modifier and when a element modifier?

When a modifier affects the whole module, e.g. it changes the color scheme of (potentially) all elements in the module, then a block modifier should be used.

.badge
  background: $black

  // Light version
  &-light
    background: $white

.badge_inner
  color: $gray

  // Light version
  .badge-light &
    color: $black

.badge_title
  color: $primary

  // Light version
  .badge-light &
    color: $secondary

When a modifier affects only a specific element of the module, e.g. it changes the size of the title, then a element modifier should be used.

.c-badge
  ...

.c-badge_title
  font-size: $fs-large

  // Small title variations
  &-small
    font-size: $fs-small

Other things to consider about modifiers

When a module modifier has to heavily overwrite the default styles consider creating two modifiers instead: One that contains the default styles (because probably they should not be default styles) and one that contains the new modifier’s styles.

Modifiers should only slightly alter the default styles of a module. If every child element in the module needs heavy modification consider creating a separate module. It is better to have some duplicated code that is independent form each other than maintaining code that relies too heavily on modifiers.

It is up to the style author to decide what way to go, two modifiers or two modules.

Other concepts

The rem unit.

All spacing is set using the rem unit. Using a relative unit is good for accessibility as it allows users that are visually impaired to scale the page via their default browser settings. To make working with rem units more comfortable the font-size of the html element is set to 62.5% which equals 10px. This means that for example 1rem == 10px or 2.2rem === 22px.

.js- classes and state-classes

To select elements with Javascript only use classes with the prefix .js-. On the other hand never use a .js- class for styling.

This separation of concerns will enable developers to make changes to both CSS and Javascript with much more confidence and makes refactoring much easier.

Ideally .js- class names should follow a similar BEM-naming approach as explained above.

Of course there are some dependencies between Javascript and CSS especially when classes are applied to DOM elements using Javascript. These classes are called state-classes as usually they provoke a change in the state of the UI.

State classes should start with a .is- or a .has- and should always be scoped to the CSS module they modify as they can be quite generic e.g. .is-open, .is-hidden:

.c-badge
  // Disabled badge 
  &.is-disabled
  ...

// This can also be OK
.c-badge_inner
  // Disabled badge 
  .c-badge.is-disabled &

The $space variable

The $space variable equals the general line height of text. This allows to have a consistent measuring unit contained in a variable which will help to decide on the spacing of elements. Another big benefit is that it allows to “scale” the website spacing-styles (if used consistently) with the change of a single variable. This might not be something that will always be needed but it is a good thing to have.

Without $space:

.wdg-myBlock
  // equals the line-height
  margin-bottom: 2.2rem

// Other file
.wdg-myOtherBlock
  // equals half of the line-height
  margin-bottom: 1.1rem

Now if the line-height was to change, two files would need to be modified to fulfill the change, with potentially tens of files, this will not be very scalable and will be very error prone.

.wdg-myBlock
  // equals the line-height
  margin-bottom: $space

// Other file
.wdg-myOtherBlock
  // equals half of the line-height
  margin-bottom: $space-half

// Other file
.c-label
  // This is also OK
  padding: $space + 0.1rem

// Inside vars
// Changes need to only be made here.
$space: 2.2rem

Sometimes after changing the value of $space some styles might need to be readjusted, especially styles like $space + 0.1rem, but for the most part it will be fine and it is far better than changing hundreds of hard-coded values throughout the code base.

When not to use $space

Mainly $space should be used to create consistent spacing between elements. Some modules might need spacing “inside”, especially if they are complex, interactive UI pieces, that don’t have much to do with the rest of the layout. In that case using hard-coded values is OK.

Magic numbers

If hard-coded values, a.k.a. magic numbers are relevant for other modules in your system they should be stored in a variable e.g. $header-height: 8rem.

// In _wdg-header
.wdg-header
  position: fixed
  height: $header-height

// In defaults
body
  padding-top: $header-height

The .typography class

Text that created by editors in the CMS using a WYSIWYG editor is output in a single variable (often named $Content). The text just contains raw HTML elements, to style this text it is wrapped by the .typography class.

<div class="typography">
  $Content
</div>

This allows to control how (raw) HTML text is presented and to differentiate it from the rest of the website’s interface. The appearance of media embedded in the text using the WYSIWYG is also controlled via this class.

In SilverStripe the .typography class is also added to the <body> element of each WYSIWYG editor in the CMS, wich in turn will load the editor.css file to make the appearance of the text be the same in the CMS and the public site where the text is output.

The _shame.sass file

All temporary hacks or quick fixes should be in this file. The content of this file should be evaluated periodically by a CSS developer that can then sort in and properly re-factor theses styles into the code base or delete them. Styles in this file should always have comments that explain their purpose!

Each module should take care of looking good when printed. There are a couple of helpers in place for optimizing printing:

Print grid:

<div class="g-row">
  <div class="g-4@sm g-6@print"> <!-- Will look have 6 columns when printed -->
    ...
  </div>
</div>

Print media query:

.myElement
  ...
  +mq($bp-sm)
    // Styles for $bp-sm breakpoint

  +mq("print")
    // Styles for printing

There are also some utility classes that are meant for printing. Have a look at the source code to learn more.