Organising CSS at scale

CSS · Advanced · 12 min

CSS is global by default; without structure, every change risks breaking a distant page.

Every CSS rule is global, so large projects need conventions to stay maintainable. The approaches overlap, and modern codebases mix them.

Naming conventions. BEM names classes as block, element and modifier: .card, .card__title, .card--featured. Every selector is a single class, so specificity stays flat, and the names show where a style belongs.

Scoping. CSS Modules or framework-scoped styles (Vue, Svelte, Angular) rewrite class names per component. Shadow DOM isolates web components. Native @scope (.card) to (.card-content) { ... } limits rules to a subtree with a lower boundary, and scoping proximity makes the nearest scope win when two scopes conflict.

Utility-first. Frameworks like Tailwind use small single-purpose classes in the markup, which avoids naming and dead CSS at the cost of longer class lists.

Design tokens. Spacing, colour and type scales defined once as custom properties keep everything consistent and make theming possible.

Layers. @layer reset, base, components, utilities sets precedence explicitly.

General rules:

  • Keep specificity low and flat: classes, not ids or long chains.
  • Style components independently of where they appear; let parents handle layout (gap, grid) instead of children setting outer margins.
  • Co-locate styles with components and delete them with the component.
  • Lint with Stylelint and let a formatter handle style.

Example

@layer reset, base, components, utilities;

@layer components {
  /* BEM: one class per selector, flat specificity */
  .card { padding: var(--space-4); border-radius: var(--radius-md); }
  .card__title { font-size: var(--text-lg); }
  .card--featured { border: 2px solid var(--color-accent); }
}

/* @scope: styles apply inside .card but stop at .card__content */
@scope (.card) to (.card__content) {
  img { border-radius: var(--radius-md); }
}

/* layout belongs to the parent, not to the children's margins */
.card-list { display: grid; gap: var(--space-4); }

Remember

  • Global CSS needs conventions: naming, scoping or utilities
  • Keep specificity flat with single-class selectors
  • Tokens + layers give consistency and predictable precedence
  • @scope limits rules to a subtree, with a lower boundary

Watch out for

  • Components that set their own outer margins, which breaks them in new contexts.

Check yourself

  1. Why does BEM recommend .card__title over .card h2?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope

Builds on

Unlocks

Nothing builds on this yet.