So this is roughly how I handle CSS these days. As much as I'd like to use CSSModules for everything, I work on a lot of different projects, for a lot of different clients. They can't all be a SPA.
Some of this seems blindingly obvious. But until I stop cleaning up messy, repetitive CSS, I figure it all merits being said.
I use one file to style HTML. This creates a baseline for the body, defines my box model, sets global typography rules, etc. If I need to style an HTML element, I style it globally. Otherwise, I give an element a class, and only style that class.
When it comes to CSS, a component is a reusable, composable chunk of UI. One or more elements, working together. Components can (and often do) contain other components.
Once I have components, I only ever write styles for components. No utilities, and no helpers.
Naming might be the hardest thing in computer science, but with BEM syntax, it's basically effortless. If you're not familiar, read up!
The biggest benefit of BEM's naming convention are it's signifiers for use. It establishes a relationship with the markup, without forcing a rigid structure. When I see panel__title
, I know that class belongs inside a .panel
element. When I see .panel .panel--primary
, I know that the styles extend from a base component, and where to look for changes.
My component classes go into a single file. No classes in any other file can override anything in here. When nothing can affect my component but itself, then I know exactly how a component will behave, regardless of parent, child, or sibling. When I need to debug a problem, I'll know exactly where to look.
The inverted triangle is a way to visualize complexity and specificity in a component's file. The top of my file starts with the most general styles and settings for my component. This code has the widest reach across the entire file. At the opposite end, the bottom of my file has the most specific conditions for my component, and has the smallest reach.
BEM helps out quite a bit with formatting and structure here. Given that each class has a unique name, there's no need to nest styles until we get down into to modifiers.
Each file should follow this general format.
- Settings - Variables
- Tools - Mixins or extendable placeholders
- Base Styles - The containing element for the component.
- Element Styles - The individual elements of that component.
- Modifier Styles - The modifications to the base component. Modifier Classes are to be paired with the base class, and contain only changes to the base class.
- Modifier Element Styles - Style any updates to elements within that modifier. This should be the first use of nested classes in your file.
- Media Queries - We effectively start over from the top. Within the scope of the media query, style the Base class, then your Element classes, then your Modifiers, then your Modifier Elements.
When we write with the cascade, we often only ever write very small conditional updates. This helps manage complexity, and keeps the scope of our updates very apparent. Anything written at the top of the file will affect everything beneath it. Anything written at the bottom of the file will often only affect itself.
A note on Mobile-First CSS
Layouts tend to gain complexity as they get wider. Most mobile layouts boil down to stacked containers and full-width elements. Wider layouts have the luxury of space. A view often adds complexity when switching to a more horizontally-focused hierarchy.
I start by writing my CSS for the smallest viewport that we support. This way, I can write all screen-focused media-queries with min-width
.
See panel.scss as a working example.
If you're not writing CSS implementing the principles presented here, you're gonna have a baaad tiiiime.