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.
main.sass
fileMain stylesheet file that will be compiled to main.css
. It only imports partials and never has any styles of its own.
editor.sass
filesWill 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
folderContains 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
folderContains all base styles: raw HTML elements, fonts, breakpoint helper and generated sprite styles (learn more about sprites here).
thirdparty
folderContains styles that are meant to style third-party components, mainly javascript widgets that have their own (un-changeble) class name structure.
layout
folderContains 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
folderContains styles specific to the SilverStripe CMS, mainly form styles as their class names and structure is given by the system.
modules
folderContains 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
folderContains 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.
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.
Use 2 spaces for indentation.
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 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")
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
<input>
need to be styled using attribute selectors: <input type="submit">
<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..myElement
color: red
// Apply hover style only if the HTML element is a link.
a.&:hover
color: green
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.
.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.
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:
.l-
- Layouts classes.g-
- Grid classes.u-
- Utility classesNote: There is also a another name-space: .js-
. Learn more about it here.
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>
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
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).
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:
.badge-light
.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 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.
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-classesTo 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 &
$space
variableThe $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.
$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.
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
.typography
classText 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.
_shame.sass
fileAll 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.