Codeskill

Learn to code, step by step

CSS architecture – BEM, ITCSS, and utilities

CSS architecture: BEM, ITCSS, and utility-class hybrids. The goal is organised stylesheets that another developer can navigate, not a holy war about naming. Pick patterns that fit the project size and team, then stick to them.

Why architecture matters

Small sites survive one styles.css. Real projects accumulate components, overrides, third-party CSS, and six months of ‘just this once’ rules. Without structure, specificity creeps, duplicate styles appear, and nobody knows where to add the next button variant.

BEM – Block Element Modifier

BEM names describe component structure:

  • Block – standalone component (.card)
  • Element – part of a block (.card__title)
  • Modifier – variant or state (.card--featured, .card__title--large)
.card { … }
.card__media { … }
.card__body { … }
.card__title { … }
.card--horizontal { … }
.card--featured { … }

Class names get long. In return, you avoid deep nesting in selectors and know exactly what a rule affects. Pair with single-class specificity or :where() on the block wrapper if overrides should stay easy.

ITCSS – inverted triangle layers

ITCSS orders CSS from low to high specificity, generic to explicit:

  1. Settings – variables, design tokens
  2. Tools – mixins, functions (SCSS layer)
  3. Generic – reset, box-sizing
  4. Elements – bare tags (a, h1)
  5. Objects – layout patterns (.o-layout)
  6. Components – UI pieces (.c-card)
  7. Utilities – single-purpose helpers (.u-text-center)

Maps neatly to @layer in plain CSS. File folders often mirror the layers: settings/, components/, utilities/.

Utility classes

Utilities do one job: .mt-4, .flex, .sr-only. Tailwind popularised the approach; you can write a small utility set without adopting a framework:

@layer utilities {
  .flex { display: flex; }
  .gap-4 { gap: 1rem; }
  .items-center { align-items: center; }
  .text-center { text-align: center; }
  .sr-only {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}

Utilities shine for one-off layout tweaks in templates. They become painful when every element carries twelve classes and the HTML is unreadable.

A sensible hybrid

Most Codeskill-sized projects do well with:

  • Tokens on :root (settings)
  • BEM-ish components for repeated UI
  • A thin utility layer for spacing and visibility helpers
  • @layer to enforce order
@layer reset, base, layout, components, utilities;

@layer components {
  .site-header { … }
  .button { … }
  .card { … }
}

@layer utilities {
  .stack > * + * {
    margin-block-start: var(--space-stack);
  }
}

Naming conventions that help

Whatever system you pick, be consistent:

  • Prefix layout objects if you use them (l- or o-)
  • Prefix components if multiple authors (c-) or skip prefixes on small sites
  • State classes: .is-open, .is-active, or data attributes ([aria-expanded="true"]) styled directly
  • JavaScript hooks: js-toggle-menu – never style these; JS and CSS decouple

What to avoid

  • Deep nesting in SCSS that compiles to high-specificity selectors
  • Global element rules that leak into components (div { … })
  • !important as default conflict resolution
  • Rewriting architecture mid-project without a migration plan

Document the decision

A short README in your CSS folder beats oral tradition: layer order, naming examples, where new components live, when a utility is OK vs when to create a component class. Future you is another developer.

The next tutorial covers working with SCSS output sanely – reading compiled CSS, source maps, and hand-off from the SCSS tutorials.

PreviousUser preferences – reduced motion and colour scheme