Skip to Content

CSS/SCSS Style Guide

The following document outlines a reasonable style guide for CSS development.

General Principles

  • Don’t try to prematurely optimize your code; keep it readable and understandable.
  • All code in any code-base should look like a single person typed it, even when many people are contributing to it.
  • Don’t use !important unless you really need it. It’s a sign of bad code. Specificity is your friend.
  • Don’t use id selectors. They are too specific and can’t be reused.
  • Don’t write inline styles. They are hard to maintain and override. Use classes instead.
  • Don’t use @import to import styles in SCSS. It’s slow and can cause problems with specificity.

Comments

Well commented code is extremely important. Take time to describe components, how they work, their limitations, and the way they are constructed. Don’t leave others in the team guessing as to the purpose of uncommon or non-obvious code.

Comment style should be simple and consistent within a single code base.

  • Place comments on a new line above their subject.
  • Keep line-length to a sensible maximum, e.g., 80 columns.
  • Make liberal use of comments to break CSS code into discrete sections.
  • Use “sentence case” comments and consistent text indentation.

Examples:

/* ========================================================================== Section comment block ========================================================================== */
/* Sub-section comment block ========================================================================== */
/** * Short description using Doxygen-style comment format * * The first sentence of the long description starts here and continues on this * line for a while finally concluding here at the end of this paragraph. * * The long description is ideal for more detailed explanations and * documentation. It can include example HTML, URLs, or any other information * that is deemed necessary or useful. * * @tag This is a tag named 'tag' * * TODO: This is a todo statement that describes an atomic task to be completed * at a later date. It wraps after 80 characters and following lines are * indented by 2 spaces. */
/* Basic comment */

Format

The chosen code format must ensure that code is: easy to read; easy to clearly comment; minimizes the chance of accidentally introducing errors; and results in useful diffs and blames.

  • Use one discrete selector per line in multi-selector rulesets.
  • Include a single space before the opening brace of a ruleset.
  • Include one declaration per line in a declaration block.
  • Use one level of indentation for each declaration.
  • Include a single space after the colon of a declaration.
  • Use lowercase and shorthand hex values, e.g., #aaa.
  • Use single or double quotes consistently. Preference is for double quotes, e.g., content: "".
  • Quote attribute values in selectors, e.g., input[type="checkbox"].
  • Where allowed, avoid specifying units for zero-values, e.g., margin: 0.
  • Include a space after each comma in comma-separated property or function values.
  • Include a semi-colon at the end of the last declaration in a declaration block.
  • Place the closing brace of a ruleset in the same column as the first character of the ruleset.
  • Separate each ruleset by a blank line.
.selector-1, .selector-2, .selector-3[type='text'] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; display: block; font-family: sans-serif; color: #333; background: #fff; background: linear-gradient(#fff, rgba(0, 0, 0, 0.8)); } .selector-a, .selector-b { padding: 10px; }

Naming

We always want to ensure that all of our classes and settings are meaningfully named and adhering to our set conventions and to not worry about the length of our class names as gzip will compress well written code incredibly well.

We use;

  • lowercase
  • BEM-like naming for most classes

We don’t want;

  • CamelCase
  • underscores with the exception of being used in BEM Element selectors
  • ids, never

BEM-like Naming

We apply BEM as described in the BEM-like naming  from CSS Guidelines with one minor change. For modifiers, we use -modifier instead of block__element--modifier. It’s easier to read and write.

/* Block component */ .site-search { } /* Element that depends upon the block */ .site-search__field { } /* Modifier that changes the style of the block */ .site-search__field-full { }

Declaration order

If declarations are to be consistently ordered, it should be in accordance with a single, simple principle.

.selector { /* Positioning */ position: absolute; z-index: 10; top: 0; right: 0; bottom: 0; left: 0; /* Display & Box Model */ display: inline-block; overflow: hidden; box-sizing: border-box; width: 100px; height: 100px; padding: 10px; border: 10px solid #333; margin: 10px; /* Other */ background: #000; color: #fff; font-family: sans-serif; font-size: 16px; text-align: right; }

Large blocks of single declarations can use a slightly different, single-line format. In this case, a space should be included after the opening brace and before the closing brace.

.selector-1 { width: 10%; } .selector-2 { width: 20%; } .selector-3 { width: 30%; }

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. There are various formats that could be used; one example is shown below.

.selector { background-image: linear-gradient(#fff, #ccc), linear-gradient(#f3c, #4ec); box-shadow: 1px 1px 1px #000, 2px 2px 1px 1px #ccc inset; }

SCSS

  • Limit nesting to 1 level deep. Reassess any nesting more than 2 levels deep. This prevents overly-specific CSS selectors.
  • Avoid large numbers of nested rules. Break them up when readability starts to be affected. Preference to avoid nesting that spreads over more than 20 lines.
  • Always place @extend statements on the first lines of a declaration block.
  • Where possible, group @include statements at the top of a declaration block, after any @extend statements.
  • Consider prefixing custom functions with x- or another namespace. This helps to avoid any potential to confuse your function with a native CSS function, or to clash with functions from libraries.
.selector-1 { @extend .other-rule; @include clearfix(); @include box-sizing(border-box); width: x-grid-unit(1); // other declarations }

Practical Example

Here’s an example where we demonstrate the above principles in action.

/* ========================================================================== Grid layout ========================================================================== */ /** * Column layout with horizontal scroll. * * This creates a single row of full-height, non-wrapping columns that can * be browsed horizontally within their parent. * * Example HTML: * * <div class="grid"> * <div class="cell cell-3"></div> * <div class="cell cell-3"></div> * <div class="cell cell-3"></div> * </div> */ /** * Grid container * * Must only contain `.cell` children. * * 1. Remove inter-cell whitespace * 2. Prevent inline-block cells wrapping */ .grid { height: 100%; font-size: 0; /* 1 */ white-space: nowrap; /* 2 */ } /** * Grid cells * * No explicit width by default. Extend with `.cell-n` classes. * * 1. Set the inter-cell spacing * 2. Reset white-space inherited from `.grid` * 3. Reset font-size inherited from `.grid` */ .cell { position: relative; display: inline-block; overflow: hidden; box-sizing: border-box; height: 100%; padding: 0 10px; /* 1 */ border: 2px solid #333; vertical-align: top; white-space: normal; /* 2 */ font-size: 16px; /* 3 */ } /* Cell states */ .cell.is-animating { background-color: #fffdec; } /* Cell dimensions ========================================================================== */ .cell-1 { width: 10%; } .cell-2 { width: 20%; } .cell-3 { width: 30%; } .cell-4 { width: 40%; } .cell-5 { width: 50%; } /* Cell modifiers ========================================================================== */ .cell-detail, .cell-important { border-width: 4px; }