CSS is mainly written via Sass pre-processor at Chefkoch, though some projects may deviate from this. We follow a cherry-picked mixture of cssguidelin.es and sass-guidelin.es for writing our SCSS files. All example snippets are sourced from sass-guidelin.es / created by Hugo Giraudel / CC BY 4.0
At a very high-level, we want:
- four (4) space indents, no tabs
- multi-line CSS
- meaningful use of whitespace
// Yep
.foo {
display: block;
overflow: hidden;
padding: 0 1em;
}
// Nope
.foo {
display: block; overflow: hidden;
padding: 0 1em;
}
Although neither CSS nor Sass do require strings to be quoted, we recommend that strings should always be wrapped with single quotes ('
) in Sass. Besides consistency with other languages, including CSS’ cousin JavaScript, there are several reasons for this choice:
- color names are treated as colors when unquoted, which can lead to serious issues
- most syntax highlighters will choke on unquoted strings
- it helps general readability
- there is no valid reason not to quote strings
// Yep
$direction: 'left';
// Nope
$direction: left;
// Yep
$font-type: sans-serif;
// Nope
$font-type: 'sans-serif';
If a string contains one or several single quotes, one might consider wrapping the string with double quotes ("
) instead, in order to avoid escaping characters within the string.
// Okay
@warn 'You can\'t do that.';
// Okay
@warn "You can't do that.";
URLs should be quoted as well, for the same reasons as above:
// Yep
.foo {
background-image: url('/images/kittens.jpg');
}
// Nope
.foo {
background-image: url(/images/kittens.jpg);
}
In Sass, number is a data type including everything from unitless numbers to lengths, durations, frequencies, angles and so on. This allows calculations to be run on such measures.
Numbers should display leading zeros before a decimal value less than one. Never display trailing zeros.
// Yep
.foo {
padding: 2em;
opacity: 0.5;
}
// Nope
.foo {
padding: 2.0em;
opacity: .5;
}
When dealing with lengths, a 0 value should never ever have a unit.
// Yep
margin: 0;
// Nope
margin: 0px;
Beware, this practice should be limited to lengths only. Having a unitless zero for a time property such as transition-delay is not allowed. Theoretically, if a unitless zero is specified for a duration, the declaration is deemed invalid and should be discarded. Not all browsers are that strict, but some are. Long story short: only omit the unit for lengths.
Note: There are more good practices dealing with numbers at http://sass-guidelin.es/#numbers and we recommend to follow them.
When using a color more than once, store it in a variable (prefixed with $color-
) with a standardized name representing the color like $color-sushi
(use chir.ag's Name That Color to find the name). Since this method leads to unique color names which we do not want to spread around our code base (cannot reuse when the color changes), you need to map this color to an abstract second variable like $color-brand-primary.
David Walsh has explained at length why this seemingly convoluted approach is a good idea: Sass Color Variables That Don’t Suck.
$color-sushi: #7ba344;
[…]
$color-brand-primary: $color-sushi;
Here is how a (S)CSS ruleset should be written (at least, according to most guidelines, including cssguidelin.es):
- related selectors on the same line; unrelated selectors on new lines
- the opening brace (
{
) spaced from the last selector by a single space - each declaration on its own new line
- a space after the colon (
:
) - a trailing semi-colon (
;
) at the end of all declarations - the closing brace (
}
) on its own new line - a new line after the closing brace (
}
)
// Yep
.foo, .foo--bar,
.baz {
display: block;
overflow: hidden;
margin: 0 auto;
}
// Nope
.foo,
.foo-bar, .baz {
display: block;
overflow: hidden;
margin: 0 auto }
Adding to those CSS-related guidelines, we want to pay attention to:
- local variables being declared before any declarations, then spaced from declarations by a new line
- mixin calls with no
@content
coming before any declaration - new line after mixin calls with not
@content
that are followed by a declaration - nested selectors always coming after a new line
- mixin calls with
@content
coming after any nested selector - no new line before a closing brace (
}
)
// Yep
.foo, .foo-bar,
.baz {
$length: 42em;
@include ellipsis;
@include size($length);
display: block;
overflow: hidden;
margin: 0 auto;
&:hover {
color: red;
}
@include breakpoint('$my-breakpoint') {
overflow: visible;
}
}
There is a lot of discussion around the order of declarations. The different factions are alphabetical order, order by type and fuck it, random. Find an approach that works for you and your team until we have implemented automated declaration sorting with either csscomb.com or stylelint.io (with stylefmt).
One particular feature Sass provides that is being overly misused by many developers is selector nesting. Selector nesting offers a way for stylesheet authors to compute long selectors by nesting shorter selectors within each others.
We recommend to avoid selector nesting as much as possible for reasons given in Beware of Selector Nesting and Avoid nested selectors for more modular CSS (tl;dr: better reuse, better maintainability). The BEM naming convention for HTML classes helps a lot in this regard.
It can make sense to use moderate nesting, but please keep to the Inception rule(never nest more than 3 levels deep).
Of course, there are cases where nesting is not only allowed, but recommended:
- use selector nesting for pseudo-classes and pseudo-elements
- use selector nesting for component-agnostic state classes such as .is-active
// Yep
.foo {
color: red;
&:hover {
color: green;
}
&::before {
content: 'pseudo-element';
}
&.is-active {
font-weight: bold;
}
}
Last but not least, it may make sense to use the reverse parent selector when styling an element because it happens to be contained within another specific element. This can help to keep everything about the component in the same place.
// Yep
.foo {
// …
.no-opacity & {
display: none;
}
}
This will compile to:
// CSS
.no-opacity .foo {
display: none;
}
We recommend to closely follow the naming conventions proposed by cssguidelin.es, especially BEM-like Naming.
_BEM, _meaning Block, Element, Modifier, is a front-end methodology coined by developers working at Yandex. Whilst BEM is a complete methodology, here we are only concerned with its naming convention. Further, the naming convention here only is BEM-like; the principles are exactly the same, but the actual syntax differs slightly.
BEM splits components’ classes into three groups:
- Block: The sole root of the component
- Element: A component part of the Block
- Modifier: A variant or extension of the Block
To take an analogy (note, not an example):
.person {}
.person__head {}
.person--tall {}
Elements are delimited with two (2) underscores (__
), and Modifiers are delimited by two (2) hyphens (--
).
Here we can see that .person {}
is the Block; it is the sole root of a discrete entity. .person__head {}
is an Element; it is a smaller part of the .person {}
Block. Finally, .person--tall {}
is a Modifier; it is a specific variant of the .person {}
Block.
**Please note: **If we were to add another Element—called, let’s say, .person__eye {}
—to this .person {}
component, we would not need to step through every layer of the DOM. That is to say, the correct notation would be .person__eye {}
, and not .person__head__eye {}
. Your classes do not reflect the full paper-trail of the DOM.
Hugo Giraudel has explained BEM syntax regarding nested elements in more detail, while Harry Roberts clarifies when to use a BEM modifier vs. a stateful class.