In working on large, long running projects with dozens of developers, it is important that we all work in a unified way in order to, among other things:
- Keep stylesheets maintainable.
- Keep code transparent and readable.
- Keep stylesheets scalable.
No matter the document, we must always try and keep a common formatting. This means consistent commenting, consistent syntax and consistent naming.
There are a variety of techniques we must employ in order to satisfy these goals.
- Whitespace
- Formatting
- Naming conventions
- Comments
- Architecture Overview
- Writing CSS
- Acknowledgements and Further Reading
Here are our rules for managing whitespace in Sass files:
- Use soft indents (spaces).
- Use 2 characters for the indentation level.
- Never mix spaces and tabs for indentation.
- Wrap at 80 characters wide.
Where possible, limit CSS files’ width to 80 characters. Reasons for this include:
- The ability to have multiple files open side by side.
- Viewing CSS on sites like GitHub, or in terminal windows.
- Providing a comfortable line length for comments.
There will be unavoidable exceptions to this rule—such as URLs, or gradient syntax—which shouldn’t be worried about.
Configure your text editor to adhere to the above. At the bare minimum configure your editor to "show invisibles" or to automatically remove end-of-line whitespace.
If you use Sublime Text editor then install this package and use these settings in your Settings - User file which can be found here: Sublime Text -> Preferences -> Settings User:
"tab_size": 2,
"translate_tabs_to_spaces": true,
"trim_automatic_white_space": true,
"trim_trailing_white_space_on_save": true,
"word_wrap": true,
"wrap_width": 80
This is optional:
"rulers":
[
80
],
Our chosen CSS code formatting rules ensures that CSS code is:
- Easy to read.
- Easy to clearly comment.
- Minimises the chance of accidentally introducing errors.
- Results in useful diffs and blames.
For reference here is an anatomy of a rule set:
[selector] {
[property]: [value];
[<- Declaration ->]
}
-
Class names to use BEM notation, see Naming Conventions -> BEM, where BEM isn't used then hyphen delimited class names are to be used.
-
Use one discrete selector per line in multi-selector rule sets.
Good
.error, .success { ... }
Bad
.error, .success { ... }
-
Include a single space before the opening brace of a rule set.
Good
.error, .success { ... }
Bad
.error, .success{ ... }
-
Place the closing brace of a rule set in the same column as the first character of the rule set.
Good
.error, .success { ... }
Bad
.error, .success { ...}
-
Properties within rule sets should each reside on their own line.
Good
p { margin: 0; padding: 0; }
Bad
p {margin: 0; padding: 0;}
-
Use one level of indentation for each declaration.
Good
.error { border: 0; margin: 0; }
Bad
.error { border: 0; margin: 0; }
-
Separate each rule set by a blank line.
Good
.error { border: 0; margin: 0; } .success { border: 0; margin: 0; }
Bad
.error { border: 0; margin: 0; } .success { border: 0; margin: 0; }
-
Include a single space after the colon of a property.
Good
margin: 0;
Bad
margin:0;
-
Use lowercase and shorthand hex values.
Good
#aaa
Bad
#aaaaaa
-
Always use the shortest shorthand form possible for properties that support it.
Good
margin: 1px;
Bad
margin: 1px 1px 1px 1px;
-
Always use double quotes, specifically for:
- String literals e.g.
content: "";
. url()
e.g.background: url("img/logo.png");
.- Attribute values in selectors e.g.
input[type="checkbox"]
.
- String literals e.g.
-
Where allowed, avoid specifying units for zero-values.
Good
margin: 0;
Bad
margin: 0px;
-
Commas in lists should be followed by a space.
Good
color: rgba(0, 0, 0, 0.1);
Bad
color: rgba(0,0,0,0.1);
-
Include a space before
!important
keyword.Good
padding: 10px !important;
Bad
padding: 10px!important;
-
Property values;
@extend
,@include
, and@import
directives; and variable declarations should always end with a semicolon.Good
color: #fff;
Bad
color: #fff
-
Parentheses should not be padded with spaces.
Good
@include box-shadow(0 2px 2px rgba(0, 0, 0, .2));
Bad
@include box-shadow( 0 2px 2px rgba( 0, 0, 0, .2 ) );
-
When a decimal mark is needed always include the zero.
Good
0.25rem
Bad
.25rem
-
Don't write trailing zeros for numeric values with a decimal point.
Good
margin: 0.5em;
Bad
margin: 0.500em;
-
url
s should not contain protocols or domain names.Good
background: url('assets/image.png');
Bad
background: url('https://example.com/assets/image.png');
Declarations should be ordered alphabetically.
Example:
.selector {
background-color: #000;
bottom: 0;
box-sizing: border-box;
color: #fff;
display: inline-block;
font-family: sans-serif;
font-size: 1em;
height: 100px;
text-align: right;
width: 100px;
}
Long, comma-separated property values—such as collections of gradients or shadows can be arranged across multiple lines in an effort to improve readability and produce more useful diffs. Each value after the first should be indented so that it starts at the same level as the first value e.g.
.selector {
background-image: linear-gradient(#fff, #ccc),
linear-gradient(#f3c, #4ec);
box-shadow: 1px 1px 1px #000,
2px 2px 1px 1px #ccc inset;
}
All our CSS is written with the Sass preprocessor so our conventions should be extended to accommodate the particularities of Sass.
-
Limit nesting to 1 level deep. Reassess any nesting more than 2 levels deep. This prevents overly-specific CSS selectors (see Specificity).
-
Always use placeholder selectors in
@extend
. Using a class selector with the@extend
statement statement usually results in more generated CSS than when using a placeholder selector. -
Do not use parent selector references (
&
) when they would otherwise be unnecessary, e.g..foo { > .bar {} }
not
.foo { & > .bar {} }
-
Rule sets should be ordered as follows:
@extend
declarations.@include
declarations without inner@content
.- Properties.
@include
declarations with inner@content
.- Nested rule sets.
To ensure these rules are met you should use the .scss-lint
, see. And intergrate linting into some sort of automated testing e.g. setup up a task in a Rakefile.
N.B. because we indent rule sets to mirror the DOM the linting tool will flag this as a warning e.g.
utilities/_u-flex-embed.scss:69 [W] Indentation: Line should be indented 0 spaces, but was indented 2 spaces
You can safely ignore this.
If you use Sublime Text editor then install this package which provides an interface to the .scss-lint
tool.
Always ensure classes are sensibly named; keep them as short as possible but as long as necessary. Ensure any utilities are very vaguely named (e.g. .u-display-block
, .u-divider
) to allow for greater reuse. Don’t worry about the amount or length of classes in your markup; gzip will compress well written code incredibly well.
ID's cannot be used as style hooks, see.
We use the BEM (Block, Element, Modifier) naming convention. When BEM is not used e.g. JavaScript hooks then we use hyphen delimited classes e.g. .foo-bar
, not .foo_bar
or .fooBar
.
BEM is a methodology for naming and classifying CSS selectors in a way to make them a lot more strict, transparent and informative.
The naming convention follows this pattern:
.block{}
.block__element{}
.block--modifier{}
.block
represents the higher level of an abstraction or component..block__element
represents a descendent of.block
that helps form.block
as a whole..block--modifier
represents a different state or version of.block
.
Any type of style that is state based e.g. a navigation link may be in an active state, an accordion section may be in an expanded state, should be namespaced with .is-
, e.g. .is-active
, .is-expanded
.
State styles 90% of the time have a JavaScript/server-side dependency e.g. to highlight the active link in some primary navigation we will append .is-active
to the relevant a
element via server-side code, to style a component differently based on whether it only has one list item we would write some JS to check for this and if it's true append .is-only-item
to the parent component element.
To keep things consistent we use a set list of common state class names:
is-active
is-loaded
is-loading
is-visible
is-only-item
is-only-items-x
- where x is the amount of itemsis-disabled
is-expanded
is-collapsed
Where possible use these names rather than creating your own and we can always use these names as state styles are always scoped to the thing they're being applied too.
State hooks do not use BEM.
Never use a CSS styling class as a JavaScript hook. Attaching JS behaviour to a styling class means that we can never have one without the other.
If you need to bind to some markup use a JS specific CSS class which is namepsaced with .js-
. So the naming convention would be something like: .js-[component]-[what-it's-doing/UI-it's-affecting]
, e.g. .js-accordion-header
, .js-drag-and-drop
, .js-carousel-items
.
This means that we can attach both JS and CSS to classes in our markup but there will never be any troublesome overlap.
A common practice is to use data-* attributes as JS hooks, but this is incorrect. data-* attributes, as per the spec, are used to store custom data private to the page or application (emphasis mine). data-* attributes are designed to store data, not be bound to.
JS hooks do not use BEM.
In addition to JS hooks we have Test and Tracking hooks.
Test hooks are used for back-end tests e.g. testing some Ruby logic.
Tracking hooks are used for tracking elements for analytics.
They're simply classes namespaced like so: .test-
(test) / .track
(tracking) and follow the same naming convention as JS Hooks e.g. .test-[component]-[UI-it's-affecting]
/ .track-[component]-[UI-it's-affecting]
.
Test and Tracking hooks hooks do not use BEM.
The order of the above hooks, within the class
attribute, should be:
- Style hooks:
- Component hook (no namespace)
- Layout hook (
.l-
) - Utility hook (
u-
)
- JS hooks (
.js-
) - Test hooks (
.test-
) - Tracking hooks (
.track-
)
Example:
<div class="pagination l-container u-text-align-center js-paginate test-pagination ga-pagination">
We should document and comment our code as much as we possibly can, what may seem or feel transparent and self explanatory to you may not be to another dev. Write a chunk of code then write about it.
For most of our comments we use a DocBlock-esque style comment e.g.
/**
* Settings.
*/
A DocBlock comment begins with /**
and has an *
at the beginning of every line. Any line within a DocBlock that doesn't begin with a *
will be ignored, this technique is primarily used when referencing code so that the code can easily be copied to the clipboard e.g.
/**
* N.B. This utility requires that you remove the whitespace between `li`s
* especially with the Spacing modifiers. One way to remove whitespace is by
* inserting HTML comments between the opening and closing `li`s e.g.
*
<ul class="u-list-inline">
<li>Lorem</li><!--
--><li>Aliquam</li><!--
--><li>Vestibulum</li>
</ul>
*/
If you use Sublime Text editor then install this package which makes writing DocBlock style comments super easy.
Every .scss
file should always have a main title comment at the very top of the file and formatted like this:
/* ============================================================================
@COMPONENTS -> PAGINATON
========================================================================= */
The first part, prefixed with an @
symbol, represents the location of the file within the CSS architecture (see Architecture Overview). The second part represents what the .scss
file is actually styling which in most cases will be a component. It's similar to a breadcrumb pattern, so for a more deeply nested file we'll have this:
/* ============================================================================
@CORE -> MIXINS -> CONVERT PX TO EM/REM
========================================================================= */
Each main title is prefixed with an @
symbol to allow us to perform more targeted searches (e.g. grep
, etc.) and is always in uppercase.
2 blank lines should always come after a main title comment.
After the main title comes the intro block comments which consist of:
- A general description that provides an overview of what the file is doing exactly, be as detailed as you can.
- Any dependencies on other files which should always be prefixed with
N.B.
. - Sub sections prefixed with
@
:@todo
- any outstanding tasks.@demo
- a URL to a demonstration page.@markup
- markup example(s).@credit
- URL(s) to credit where the idea came from.@consideration
- any things to consider.@example
- Sass code example(s).
Examples:
/**
* Place any two elements side-by-side, typically for an image- and text-like
* content.
*
* N.B. this utility is dependant on the following utilities:
*
* - Clear fix.
* - New block formatting context.
*
* @credit
* http://www.stubbornella.org/content/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code
*/
/**
* Apply percentage based width classes with the option to apply at all the
* main breakpoints. All the classes are the same as the Width settings found
* in: Core -> Settings -> Widths, so `$one-whole` would be `.u-one-whole`.
*
* N.B. by default we're applying to the Lap breakpoint.
*/
/**
* Creates blocky list items out of a `ul` or `ol` with the option to add a
* keyline separator between the list items.
*
* @consideration
* The spacer utilities could replace this?
*
* @credit
* https://github.com/inuitcss/objects.list-block/blob/master/_objects.list-block.scss
*/
/**
* A generic drop down utility powered by some JavaScript which toggles a
* class e.g. `is-visible` on the drop down trigger (the button that makes the
* drop down visible and invisible) and the target (the actual drop down).
* This class will be used to make the drop down target visible when the
* trigger is selected. There is also a version for showing the drop down via
* the `:hover` pseudo class which is turned off for touch devices.
*
* @markup
<div class="u-drop-down">
<!-- The trigger -->
<button class="u-drop-down__trigger"> ... </button>
<!-- The target -->
<div class="u-drop-down__target"> ... </div>
</div>
*/
/**
* Generate percentage classes with the option to apply at different
* breakpoints e.g. `.u-one-whole` / `.u-lap-one-whole`. The percentage classes
* are based off the widths defined here: Core -> Settings -> Widths.
*
* N.B. the application for this mixin is quite unique so it's only used in a
* few places in the framework.
*
* @example
@include generate-percentage-classes-at-breakpoints(
$breakpoints-for-grid-push-classes,
$scally-type: "l",
$class-name: "push",
$css-property: "left"
);
*/
2 blank lines should always come after an intro block comment.
Section title comments are used to break up large .scss
files into their own logical sections so they're easier to read, however because we use compact Sass partials so extensively they're rarely needed. A section title is formatted like this:
/* Section title here
========================================================================= */
4 blank lines should be between these section titles.
Each major chunk of CSS in a file should begin with a sub title comment e.g.
/**
* Grid container.
*/
A full stop (period) should end the sub title comment.
2 blank lines should come before and 1 blank line should come after these sub title comments.
Sass allows us to use less verbose CSS comments prefixed with two forward slashes, like so: // Some comment
e.g.
h1, .h1 {
@include font-size($font-size-heading-1);
// This is needed to turn off the top margin set in normalize.css
margin-top: 0;
}
This type of comment style is used for when you need to explain what some CSS is doing, when it isn't obvious from the code itself that is, and when none of the aformentioned comment styles are appropriate.
A space should always come after the two forward slashes and when it spans multiple lines then each line should always start with the two forward slashes e.g.
// When specifying one breakpoint with an explicit limit, it needs to be
// casted into a list of lists, otherwise the mixin assumes there is a
// breakpoint called 'max'
No blank lines should come after these type of comments.
When you want to easily comment on a number of declarations in a rule set then use number labelling e.g.
/**
* Images.
*
* 1. Make responsive.
* 2. So that `alt` text is visually offset if images don't load.
*/
img {
max-width: 100%; // [1]
height: auto; // [1]
font-style: italic; // [2]
}
This should only be used when standard inline comments won't work i.e. in the above example it wouldn't be as readable if you had two inline comments for the no.1 declarations e.g.
img {
// Make responsive
max-width: 100%;
// Make responsive
height: auto;
// So that `alt` text is visually offset if images don't load
font-style: italic;
}
When working across multiple partials and in an OOCSS manner sometimes we need to make adjustments to a component that exists within another component. This should be avoided in favour of using a BEM modifier on the component itself so that you don't end up with many dependencies across your components however sometimes this isn't the most appriopiate way of handling things. So in this scenario we need to include a comment to highlight this so that other developers are aware of the relationship between the files.
This is the format to use:
/**
* Extend `.[component class]` in Components -> [component name].
*/
Whenever you're using BEM and you declare a modifier (see Naming conventions -> BEM) you need to include a comment and if required provide a brief explanation as to what it's doing, e.g.
/**
* Modifier: striped.
*
* Applies a background colour to every odd row.
*/
.u-table--striped {
tbody tr:nth-of-type(odd) td {background-color: $u-table-striped-background-cell-colour;}
}
/**
* Modifier: border.
*/
.u-table--border {
th,
td {@include to-rem(border, $u-table-border-thickness $u-table-border-style $u-table-border-colour);}
}
Any settings (Sass variables) defined in your Sass partials should always be placed before all other CSS in the .scss
file and include a comment e.g.
/**
* Settings.
*/
// Apply at these breakpoints
$u-drop-down-breakpoints: $default-breakpoints !default;
To leave reminders of outstanding work for yourself or other developers prefix the comment with TODO:
e.g.
// TODO: `.btn-main-compact-icon` needs more work
If you use Sublime Text editor it will highlight these for you.
Whenever you write some styles for non-JavaScript users always include a comment like this:
// Non-JS users
.no-js .selector {position: static;}
Whenever you write some styles that needs to use the !important
keyword always include a comment like this:
// N.B. it is okay to use `!important` here as we're doing it pre-emptively i.e. you know you will always want the rule it's applied too to take precedence.
It acts as a nice reminder to other devs that you aren't using it incorrectly i.e. reactively.
-
When commenting on specific declarations (i.e. lines) in a rule set always place the comment on a new line above the declaration (one exception to this is when using Number labelling) e.g.
// Left double quotation mark content: "\201C"; content: open-quote;
not
content: "\201C"; // Left double quotation mark content: open-quote;
-
Use backticks when referencing code e.g.
// We need to change the `box-shadow` at this breakpoint
-
Prefix important attention grabbing comments with N.B. e.g.
// N.B: have to increase the specificity by chaining the base `.btn` class to make it easy to override non-simple modifiers
-
When you end a Sass
@if
statement then follow the closing bracket with// endif
.
All of the above is about how we structure and form our CSS; they are very quantifiable rules. For how to deal with our attitude and approach to writing CSS we'll link to another set of guidelines: