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, not red-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

  1. Low specificity — prefer single-class selectors
  2. No !important except utilities or documented overrides
  3. Co-locate styles with components in modern frameworks
  4. Document conventions in README or style guide
  5. 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.