CSS Architecture
Why CSS Architecture?
Small projects need little structure. Large projects suffer from specificity wars, naming collisions, and unmaintainable stylesheets without conventions. CSS architecture provides predictable organization as teams and codebases grow.
BEM (Block Element Modifier)
/* Block */
.card { }
/* Element — part of block */
.card__title { }
.card__body { }
.card__footer { }
/* Modifier — variation */
.card--featured { }
.card__title--large { }
<article class="card card--featured">
<h2 class="card__title card__title--large">Featured</h2>
<p class="card__body">Content here.</p>
</article>
BEM Rules
- Block names describe purpose, not appearance (
user-card, notred-box) - Elements separated by
__, modifiers by-- - Modifiers used with base class:
card card--featured, never modifier alone - No nesting selectors beyond one block level
ITCSS (Inverted Triangle CSS)
Organize stylesheets from generic to specific — low to high specificity:
Settings → variables, design tokens, config
Tools → mixins, functions (Sass)
Generic → reset, normalize, box-sizing
Elements → bare HTML (h1, a, p) — no classes
Objects → layout patterns (.container, .grid, .media)
Components → UI pieces (.button, .card, .navbar)
Utilities → helpers (.text-center, .mt-4, .hidden)
Import in this order. Each layer increases specificity. Utilities may use !important as the only exception.
SMACSS Categories
Similar to ITCSS — Base, Layout, Module, State, Theme. State classes prefixed: .is-active, .is-hidden, .has-error.
CSS Modules
Scope class names automatically at build time:
/* Button.module.css */
.primary {
background: blue;
color: white;
}
import styles from './Button.module.css';
<button className={styles.primary}>Click</button>
// renders: class="Button_primary_abc123"
Prevents global namespace collisions. Supported in Vite, Webpack, Next.js.
Utility-First (Tailwind Approach)
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow">
<h2 class="text-xl font-bold">Title</h2>
<button class="px-4 py-2 bg-blue-500 text-white rounded">Action</button>
</div>
Pros: fast prototyping, no naming fatigue, consistent spacing scale
Cons: verbose HTML, requires build tooling for production purge
Design Tokens
Centralize visual decisions as CSS custom properties:
:root {
--color-primary: #007bff;
--color-text: #212529;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--font-body: 'Inter', sans-serif;
--radius-md: 8px;
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
}
Tokens enable theming, dark mode, and cross-platform design system consistency.
Specificity Management
/* Prefer */
.button { } /* 0,1,0 */
.card__title { } /* 0,1,0 */
/* Avoid */
.sidebar nav ul li a { } /* 0,0,4 — hard to override */
#header .nav .link { } /* 1,2,0 — specificity war */
Use :where() to zero specificity when needed:
:where(h1, h2, h3) { margin-top: 0; } /* 0 specificity */
File Organization Example
styles/
├── settings/_variables.css
├── generic/_reset.css
├── elements/_typography.css
├── objects/_layout.css
├── components/_button.css
├── components/_card.css
└── utilities/_spacing.css
General Principles
- Low specificity — prefer single-class selectors
- No
!importantexcept utilities or documented overrides - Co-locate styles with components in modern frameworks
- Document conventions in README or style guide
- Lint CSS — Stylelint catches naming and ordering issues
Troubleshooting
Styles not applying — specificity war
- Inspect computed styles in DevTools; reduce selector weight
- Refactor to BEM single-class selectors
Duplicate styles across components
- Extract shared patterns to Objects layer or design tokens
Utility classes conflicting with components
- Load utilities last; scope component styles in CSS Modules
Pick one methodology (BEM + ITCSS is a common combo) and apply it consistently across the project.