CSS Style Guide

The more our CSS looks like it was written by a single person, the easier
it will be for our team to work with any other team member’s CSS. If we can
put the ‘code style’ part of our brain on rails, we can spend less time
tripping up on smaller stylistic choices and just make cool shit.

Table of Contents

Class Naming Methodology

We use BEM. The main reason is that it’s
popular and widely understood, which aligns with our main goal of this guide
to encourage consistency between multiple developers. The second reason is
that it creates an entirely flat selector tree with an ‘opt-in’ approach
to styling. No un-intended side effects.

If you are familiar with BEM, you’ll surely be aware of some challenges that
it brings. This section will tackle those. Here’s a
great article
about how to tackle some of those challenges. Highly recommended.

Let’s start looking at some CSS! This example shows a hypothetical card component,
which includes its HTML, SCSS, and compiled CSS.

In this example c-card is a Block, c-card__title is an Element, and
--dark is a Modifier.

The first thing to notice is that the compiled CSS file is totally flat. Its
specificity tree is just one level deep. The modifier classes override the
default classes by being further down the file, not by being more specific.

This is one of the primary technical benefits of using BEM as a methodology.
It avoids specificity wars by bypassing the natural cascade of CSS. Instead,
we apply styles directly to classes.

This means that we avoid descendant selectors. We avoid child selectors. We
avoid any selector that isn’t simply an easy to interpret (and potentially
overwrite) class selector.

This results in classes on nearly every HTML element, which may be ugly to some,
but it’s quite useful. It makes it explicitly clear to other developers what
styles are intended for each element. There should be no side effects.

Namespacing

There’s a bunch of c- prefixes on all of those class names. That’s because card
is a component. The name space makes it easy to quickly identify the function and
scope of that class.

Here’s a list of example name spaces we can use:

Namespace

Prefix

Example

Usage

Component

c-

c-card

Component or component element styles. This is how most styling should be applied.

Layout

l-

l-container

Layout specific component – grids, containers, etc.

Utility

u-

u-text-right

Single purpose class for minor utility adjustments like spacing, or alignment. Uses !important to guarantee success.

State

is-

is-active

Updates style according to component state. Usually applied to root c- elements.

JavaScript

js-

js-card

JavaScript hook to make it clear where JS is required. Never apply styling to these classes.

Template

t-

t-article

Template styles that are too specific to apply to a general component.

Page

p-

p-home

Page styles that are too specific to apply to a general template.

BEM Gotcha’s

Let’s append the previous example to show some challenges with BEM.
Let’s modify c-card__title child element and add a grand-child element inside of it.

Modifiers Affecting Child Elements

For the element c-card__title, we’ve written styles using a descendant selector.
The --dark modifier found on the parent block suggests we want the title
to be white in this instance. If you look at the selector .--dark .c-card__title {}
this ruins our flat selector tree.

That sucks. But it’s worth it.

If we wanted to be purist about it, we could have written a --light modifier and
targeted the element directly. The reason we didn’t is because it’d be a massive pain
in the ass to add another selector to every element just because the c-card is dark.

This is one of those cases where generally it’s not worth adopting a rule 100%.
Having a flat selector tree is nice, but being able to add just one class to a component’s
root (in this case, --dark) is much cleaner.

Grandchild Elements

For the element c-card__decorator inside c-card__title, we could have written it differently
as c-card__title__decorator, implying a level of hierarchy. Don’t do that. Reflecting
DOM hierarchy is not very useful in BEM, and it makes it easy to overcomplicate components.

With SCSS, we can nest by using &__element {} inside of the root component. When we do this,
it’s easy to overlook how deep the hierarchy can become. If it seems like your component has
more levels of hierarchy than it needs, consider how you might refactor it into multiple
components with distinct responsibilites.

Cross-Component Styles

Let’s say we have a card component inside of a parent hero component. Here, there are some
special styles card will need in order to work in the hero, perhaps reduced padding. So write
a modifier on card. --small might be suitable, but maybe there’s more to it. Maybe a
specific selector called --hero is required.

Architecture

From a design system standpoint, we take influence from Brad Frost and his Atomic Design
methodology. Atomic Design can be interpreted into BEM and CSS by viewing every item
as a component. Atoms, molecules, organisms: all components. Components can live
inside other components, but fussing about hierarchy won’t win us anything.

From a technical standpoint, we want to ensure fast load time for our CSS. A normal
linked CSS file will block rendering in the browser until it finishes loading, whereas
inlined CSS is part of the HTML document and renders instantly. We split up our CSS
into highStyles and lowStyles. Any partial that gets included in highStyles.scss
will be inlined on each page, whereas lowStyles.scss will compile to an external
CSS file that then gets loaded asynchronously on each page.

Declaration Rules

This is mostly rules for what goes between the curly braces. There is one very
powerful tool on our side for this one: linting. Specifically, Stylelint.
Check out .stylelintrc for our ruleset, which extends the stylelint-config-standard
and borrows a declaration order from Mobify’s code style guide.