| | |

How to choose the right CSS methodology

By Paul Michalak on
How to choose the right CSS methodology

At Bench, we started a project that unified our mobile and web codebases. While there are undoubtedly many advantages to going native, we found that the advantages of having a shared codebase outweighed the disadvantages. With a shared codebase, however, we had to define consistent internal standards that made sense and performed well in multiple environments.

When it comes to CSS, everyone seems to have their own approach to naming and organization - we’ve had a few debates about it ourselves at Bench. In this article, we’ll take a look at two of the more popular CSS conventions we considered and show you how we ended up creating our own solution.

BEM (Block, Element, Modifier)

BEM is a naming convention that has the added benefit of optimizing selector efficiency. If you’re unfamiliar with how CSS is interpreted, browsers look up CSS from right to left. So if you have something like:

.content > .column .field

Browsers will look up .field first, then check if it has a .column parent and lastly filter for .content. If everything matches up, the style is applied; otherwise it’s ignored.

The argument could be made that the less time it takes a browser to filter out invalid elements, the more efficient the selector and thus the faster the site. BEM optimizes filtering by flattening out CSS hierarchy and having as little nesting as possible, which some people may find to be a benefit of using this system. Children simply append to the parent names with a double underscore (rather than nesting), which means that you’re only ever matching a single element. Our example above would become:

.content__column__field

While selector efficiency may have been a concern in the past, it’s no longer worth worrying about in most circumstances. For anyone interested in more information on this, Ben Frain wrote an interesting article showing his profiling of selector efficiency (as well as several other performance concerns).

The problem with the BEM approach is that you end up with long names very quickly. Adding states to the mix only makes things worse. To add states in BEM, you simply add two dashes and the state name to the class you want to modify.This means you end up with something like this:

.content__column__field--active

BEM naming is intended to be more informative as it shows where a particular piece of markup is in the hierarchy and what state it’s in without having to cross reference the CSS with the HTML or vice versa. With the names above, you can tell that ‘field’ is intended to be a child of ‘column’ (and not ‘some-other-column’) and that it’s currently in an ‘active’ state. While this is an understandable goal, it results in coupling the naming of CSS classes to layout hierarchy when sufficient clarity can be achieved by making sure your modules are smaller. After some initial testing, we found that BEM-style naming quickly got out of hand. We ended up having long, bloated names not only in our CSS, but all over our HTML as well. As a result, readability suffered for an efficiency bump that was minimal.

SMACSS (Scalable and Modular Architecture for CSS)

SMACSS is an architectural approach that encourages developers to give serious thought to the site development process and organisation of modular components. The basic idea behind SMACSS is to split styles into 5 categories:

  1. Base: Default values to define styles that affect everything on the page.
  2. Layouts: Split the page into sections that contain modules.
  3. Modules: Individual, reusable components that make up the page.
  4. States: Modifiers for modules.
  5. Themes: Define a variation of style for modules.

This is just a quick and dirty breakdown of SMACSS. For a more detailed intro we recommend you read the SMACSS categorization page.

SMACSS encourages an intuitive and thoughtful approach to development. One example is its push for creating additive CSS. If you have to remove parent properties in a child class, you’re probably adding that property too early up the chain.

/* Bad CSS: */
.parent {
    border: 1px solid #000;
}

.parent .child {
    border: none;
}

We also really liked their approach to state variables. Where BEM used a double hyphen to signify a state, SMACSS simply gives an is- prefix to state names:

/* Hidden by default */
.child {
    display: none;
}

/* Shown when is-open class is added */
.child.is-open {
    display: inline;
}

The result is simple class names that help you to quickly identify state-specific properties.

On the flip side, while SMACSS gave us a great starting point, we felt like it was overcomplicating the problem. SMACSS defines larger elements such as headers, footers or grid elements as ‘layout’ components, when in reality they’re simply modules. It‘s not uncommon for a module to include submodules, so we didn’t see a need to make any distinction between top level layouts and internal layouts. Additionally, its extensive documentation (including a paid e-book) would increase the amount of time needed for new team members to get up to speed. When it came down to it, we really only ended up wanting variations of the base, module, and state portions of SMACSS.

BENCH

At Bench, our key priorities for a CSS convention were readability, modularity, and simplicity. With a fast-growing team and mandatory code reviews exposing us to new content all the time, our code style had to be easy to learn and easy to read. After discussing various options we came up with the our own style, inspired by our favourite SMACSS concepts customised for our needs:

bench-module {
    .element-class {
        font-family: $font-bold;
        font-size: $font-medium;
        background: $orange;
        ...
    }

    .element-class.is-active {
        background: $orange-dark;
        ...
    }
}

A key feature of our codebase is that every component is treated as a module using AngularJS directives. Because of this, we’re able to have simpler rules without losing readability. All of our CSS is namespaced inside the appropriate module name since selector efficiency is acceptable in these cases. We use some nesting, but only when necessary. All names are spinal-case to follow suit with CSS properties and we decided to use SMACSS-style state classes to easily map to our JavaScript. In order to enforce consistency in our designs, we use SASS variables for fonts, sizes and colours.

Adopting these rules made it simple for both Product and Platform engineers to style UI components with little technical overhead. While there is always room for improvement, we’ve found that our CSS performs well and it’s been easy to maintain consistency across our codebase. In the future, we'll have more articles on the challenges and advantages of taking a strict component-based workflow to front-end development.