CSS Architecture and BEM
Prerequisites: Cascade layers, Scoping styles
Small stylesheets work however you write them. Large ones — many developers, hundreds of components, years of changes — need rules, or every change risks breaking something elsewhere.
The problems architecture solves
- Leaking styles — a rule written for one page changes another.
- Specificity wars — overrides get more specific until
!importantis everywhere. - Fear of deleting — nobody knows if a class is still used, so CSS only grows.
- Coupling to HTML structure —
.sidebar ul li abreaks when the markup changes.
BEM
BEM (Block, Element, Modifier) is a naming convention that gives every piece of a component its own flat, single-class selector:
<article class="card card--featured">
<img class="card__image" src="rye.jpg" alt="Rye loaf" width="400" height="300">
<h2 class="card__title">Rye loaf</h2>
<p class="card__price card__price--sale">£3.50</p>
<a class="card__link button button--small" href="/rye">View</a>
</article>
.card { display: grid; gap: 0.5rem; padding: 1rem; border: 1px solid #ddd; }
.card--featured { border-color: gold; }
.card__image { width: 100%; height: auto; }
.card__title { margin: 0; font-size: 1.25rem; }
.card__price { font-weight: 600; }
.card__price--sale { color: #c62828; }
- Block — a standalone component:
card. - Element — a part of it, joined with two underscores:
card__title. - Modifier — a variation, joined with two hyphens:
card--featured,card__price--sale. - An element can combine several blocks' classes (
card__link button) — a mix.
Every selector has the same specificity (0,1,0), there's no nesting in selectors, and a class name tells you exactly where its CSS lives. The cost is verbose HTML.
Organizing files
A common structure, ordered from generic to specific (the idea behind ITCSS), maps neatly onto cascade layers:
css/
settings/ tokens: custom properties for color, spacing, type
base/ reset and element defaults (a, h1, img, ...)
layout/ page-level containers and grids
components/ one file per block: card.css, button.css, ...
utilities/ single-purpose helpers: .visually-hidden, .text-center
@layer settings, base, layout, components, utilities;
@import url("settings/tokens.css") layer(settings);
@import url("base/reset.css") layer(base);
@import url("components/card.css") layer(components);
Other approaches
- CUBE CSS — lean on global styles and composition ("Composition, Utility, Block, Exception"), with data attributes for exceptions.
- CSS Modules — write plain class names; the build tool makes them unique per component. Popular with React.
- Utility-first — next lesson.
Rules that help any approach
- Use design tokens (custom properties) instead of hard-coded values.
- One component per file; name the file after the block.
- Style classes, not elements, in component CSS.
- Don't style state with classes alone when an attribute exists:
.tab[aria-selected="true"]beats.tab--activebecause the state can't drift from the accessibility tree.