Writing better CSS with meaningful class selectors

With the rise of frameworks like React, Angular, Ember and the Web Components spec, the web seems to be moving towards a more components based approach to building apps. The abstractions for these are getting better, except for one part: CSS. In many ways, we write CSS just as we wrote it years ago. Even if we think about our UI as being more modular, the CSS is still spaghetti. Last year I was tasked with designing a CSS system for a components library and after much trial and error I found a quite satisfying pattern, that allowed for the codebase to grow in a quick and maintainable way.

Let’s start by looking at some standard spaghetti. Say we “think” about our CSS classes as “components”.

.menu {}
.overlay {}
.button {}

See the Pen QwZYwP by dpiatek (@dpiatek) on CodePen.

And we want a “modular” CSS system. So we start writing rules like this:

.menu .overlay > .button {}
#home .sidebar.managers-special {}

See the Pen PwyVqB by dpiatek (@dpiatek) on CodePen.

Stop.

Last time I checked “modular” did not mean cobble everything together with a jackhammer. All our elements are now tied together and the selector makes it ambiguous where it belongs. By side effect, it will also force our HTML to a specific structure:

See the Pen dPgaYz by dpiatek (@dpiatek) on CodePen.

It's not uncommon to mix "components" as well:

 

See the Pen PwyVPB by dpiatek (@dpiatek) on CodePen.

The "flexibility" of the language is what makes it so hard to reason about; could we avoid some of it’s features to make it better?

From my experience, the answer is yes.

Classes are the most powerful selectors because we have the most control about what they actually mean. But if that meaning is fuzzy and not respected, we lose its benefit. We need to make sure that the way we write our selectors keeps their meaning and not muddies it.

When working on a pattern library for econsultancy.com I've experimented a lot with approaches to writing such classes, and ended up with 3 rules with that help the most:

1. A CSS class always represents a component. Any other abstractions that a CSS class may represent needs a prefix, suffix or what have you - they need to be easily distinguishable and the convention needs to be well established.

Example:

// In this project, components are:
.menu {}
.menu-title {}

// Classes that modify a component start with a `-`
.-left-aligned {}
.-in-sidebar {}

// Helpers are started with `h-`
.h-margin-bottom {}

// Theming classes are started with `t-`
.t-brand-color {}

See the Pen NPOoGo by dpiatek (@dpiatek) on CodePen.

2. All CSS selectors that contain a CSS component class must start with it and they must not modify any other CSS component class or it’s children directly (by referencing it - so a selector can ever include only a single CSS component class) or indirectly (by element selectors for example).

Example:

// Ok selectors. Element selectors here would be defaults for given tags.
img {}
a {}
header {} 
.main-logo {}
.menu {}
.menu-item a {}
.menu-item img ~ a {}
.menu-item.-last {}

// Not ok
header a {}  
header .menu {}  
.menu .menu-item {}
img ~ a {}  
.menu-item img ~ a {}
.menu li:nth-child(2) a {}

See the Pen qEJgbm by dpiatek (@dpiatek) on CodePen.

3. An HTML tag can only have a single CSS component class.

Example:

As with any subset, in some cases this will make writing some of your CSS harder and more verbose. It’s probably an overkill for smallish projects, where what you need is a stylesheet and not a component library. It’s probably not well suited to drawing iPhones with box-shadows either.

Things can be done with preprocessors to simplify this approach. For example, on the aforementioned project, we used SCSS and all classes were output with mixins. This made for some crazy looking CSS, but in the end, all developers on the project found it much more easier to extend and reason about our CSS.

 

This blog post first appeared on dominikpiatek.com

Red Badger are hiring. Come join us

Dominik Piatek

Software Engineer

More from Dominik