Skip to content
AgileLogics
7 min read

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 !important is everywhere.
  • Fear of deleting — nobody knows if a class is still used, so CSS only grows.
  • Coupling to HTML structure — .sidebar ul li a breaks 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--active because the state can't drift from the accessibility tree.