hazelhq.github.io

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.

First, technology: we write in SCSS, use autoprefixer, and compile using Gulp.

Table of Contents

  1. Class Naming Methodology
  2. Architecture
  3. Comments
  4. Declaration Rules
  5. Contributing

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 don’t know BEM already, read the introduction and get up to speed.

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.

card.html

<div class="c-card c-card--dark js-card">
  <h4 class="c-card__title">
    One Weird CSS Trick You Won't Believe!
  </h4>
  <div class="c-card__content">...</div>
</div>

card.scss

.c-card { // Root component
  display: block;
  padding: $spacing-unit-1;
  background-color: $white;

  &.--dark { // Modified version of component
    background-color: $darkest-grey;
  }

  &__title { // Child element of component
    font-size: $font-size-normal;
    color: $darkest-grey;
  }
}

output.css

.c-card {
  display: block;
  padding: 1rem;
  background-color: #FFFFFF;
}

.c-card.--dark {
  background-color: #333333;
}

.c-card__title {
  font-size: 0.875rem;
  color: #333333;
}

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.

card.html

<div class="c-card c-card--dark">
  <h4 class="c-card__title">
    One <span class="c-card__decorator">Weird</span> CSS Trick You Won't Believe!
  </h4>
  <div class="c-card__content">...</div>
</div>

card.scss

.c-card {
  display: block;
  padding: $spacing-unit-1;
  background-color: $white;

  &.--dark {
    background-color: $darkest-grey;

    .c-card__title { // Modifier's descendant title
      color: $white;
    }
  }

  &__title {
    font-size: $font-size-normal;
    color: $darkest-grey;
  }

  &__decorator { // Grand-child element
    font-size: $font-size-medium;
  }
}

output.css

.c-card {
  display: block;
  padding: 1rem;
  background-color: #FFFFFF;
}

.c-card.--dark {
  background-color: #333333;
}

.c-card.--dark .c-card__title {
  color: #FFFFFF;
}

.c-card__title {
  font-size: 0.875rem;
  color: #333333;
}

.c-card__decorator {
  font-size: 1.125rem;
}

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.

It’s preferable to write card’s styles inside of card.scss. Not hero.scss.

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.

Let’s quickly look at the existing folder structure for all that:

elements/
  - exampleElement.scss
  - ...
  - ...
pages/
  - high/
    - examplePage.scss
    - ...
    - ...
  - low/
    - examplePage.scss
    - ...
    - ...
resources/
  - fonts.scss
  - ...
  - ...
errorStyles.scss
highStyles.scss
lowStyles.scss

We can now start moving gradually toward a more component driven architecture:

base/
  - type.scss
  - utilities.scss
  - ...
components/ <--- Most of the styles in here
  - exampleComponent.scss
  - ...
  - ...
layout/
  - exampleLayout.scss
  - ...
  - ...
templates/
pages/
errorStyles.scss
highStyles.scss
lowStyles.scss

Comments

Headings

We can follow some Markdown-esque methods for writing headings in comments.

// Heading Level 1
// ===============
//
// Heading Level 2
// ---------------
//
// ### Heading Level 3 and beyond

We use one line break between headings, and two line breaks between blocks.

// Example 1
// ---------
//
// Some stuff going on in here...

.styles {
  display: block;
}


// Example 2
// ---------
//
// A different example

.more-styles {
  display: inline-block;
}

Line Comments

In CSS, there are countless cases where you have to do something pretty funky to get something to work. In these cases, leave a single line comment, which SCSS accomodates by using a double backslash:

.c-card {
  /* Other styles */
  -webkit-backface-visibility: hidden; // Fixes flickering issue on Safari
}

If you find yourself repeating a line comment, turn it into a numbered comment:

// Modal
// ---
//
// Notes:
//
// 1. Absolutely position `.c-modal__inner` relative to the parent `.c-modal`

.c-modal {
    position: relative; // 1
}

.c-modal__inner {
    position: absolute; // 1
}

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.

  1. Extends
  2. Mixins/Includes (except for property specific mixins)
  3. Position
  4. Display & Box Model
  5. Visual Styles
  6. Text Styles
  7. Vendor Prefixed Styles
  8. Animations & Transitions
  9. Pseudo-classes
  10. Pseudo-elements
  11. Modifier elements
  12. Child elements

```scss .c-selector { // Extends @extend %x-extend;

// Includes
@include mixin();
// Content
content: '\25B6';
// Positioning
position: absolute;
left: 10px;
z-index: 10;
// Display & Box Model
display: inline-block;
overflow: hidden;
box-sizing: border-box;
width: 100px;
height: 100px;
margin: 10px;
padding: 10px;
border: 1px solid #333;
// Visual styles
background: #000;
border-radius: 10px;
@include box-shadow(5px 5px 0 rgba(0, 0, 0, 0);
// Text styles
color: #fff;
font-family: sans-serif;
font-size: 16px;
text-align: right;
// Vendor prefixed styles
-webkit-user-select: none;
-webkit-tap-highlight: rgba(0, 0, 0, 0);
// Styles that don't fall under any of the above categories
pointer-events: none;
// Animations & Transitions
transition: all 0.2s;
// Pseudo-classes
&:active {
    background: blue;
}
&:last-child {
    border-top: 1px solid blue;
}
// Pseudo-elements
&::before {
    content: 'CSS Rules!';
}
// Modifier Elements
&.x--light {
    background: #999;
}
// Child Elements
span {
    font-weight: bold;
} } ```

Contributing

The aim for this document is to create consistency, not rigidity. If you have an idea and want to change something here, go ahead and make a pull request with your topic and tag @rowbot-weisguy.

Thanks!