Skip to content

CSS Architecture Methodologies

advanced11 min read

The Problem Methodologies Solve

CSS has no built-in module system, no private scope (without tooling), and a global namespace. Any selector can affect any element. On a team of twenty engineers writing CSS, specificity conflicts, naming collisions, and dead code accumulate fast. Methodologies provide conventions that prevent these problems — not through tooling, but through discipline.

Each methodology has trade-offs. BEM is explicit but verbose. ITCSS manages specificity but requires upfront planning. CUBE CSS embraces the cascade. The right choice depends on your team, codebase, and tooling.

Mental Model

Think of CSS methodologies as traffic systems. Without rules, every driver (developer) takes whatever route they want — collisions happen. BEM is like a strict grid system: every road has a unique name and clearly marked intersections. ITCSS is like a highway system with layers — local roads never interfere with highways. CUBE CSS is like a well-designed city where natural flow handles 90% of traffic and explicit rules handle exceptions.

BEM (Block Element Modifier)

BEM is a naming convention that creates a flat, non-nested specificity structure:

/* Block: standalone component */
.card { }

/* Element: part of a block (double underscore) */
.card__title { }
.card__body { }
.card__image { }

/* Modifier: variation of block or element (double hyphen) */
.card--featured { }
.card__title--large { }

BEM Strengths

  • Every class name tells you exactly what it is and where it belongs
  • Flat specificity — no nesting means (0,1,0) everywhere
  • Easily searchable — .card__title appears nowhere else

BEM Weaknesses

  • Verbose class names: .navigation__item--active is 32 characters
  • No handling for utility classes, layout, or global styles
  • Deep nesting temptation: .card__header__title__icon (never do this — max one __)
/* BAD: BEM nesting (don't mirror DOM structure) */
.card__header__title { }

/* GOOD: Flatten — the element is part of the block, regardless of DOM depth */
.card__title { }

ITCSS (Inverted Triangle CSS)

ITCSS organizes CSS by specificity in layers, from broadest/lowest to narrowest/highest:

Settings    → Design tokens, variables
Tools       → Mixins, functions (preprocessor)
Generic     → Reset, normalize, box-sizing
Elements    → Bare HTML element styles (h1, p, a)
Objects     → Layout patterns (grid, media object)
Components  → UI components (card, button, modal)
Utilities   → Overrides with !important (.hidden, .text-center)

Each layer has progressively higher specificity:

/* Generic: (0, 0, 0) to (0, 0, 1) */
* { box-sizing: border-box; }
body { margin: 0; }

/* Elements: (0, 0, 1) */
h1 { font-size: 2rem; }
a { color: var(--link); }

/* Objects: (0, 1, 0) */
.o-grid { display: grid; }
.o-container { max-width: 1200px; margin-inline: auto; }

/* Components: (0, 1, 0) to (0, 2, 0) */
.c-card { padding: 1.5rem; }
.c-card.is-featured { border: 2px solid gold; }

/* Utilities: (0, 1, 0) !important */
.u-hidden { display: none !important; }
.u-text-center { text-align: center !important; }

ITCSS + Cascade Layers

ITCSS maps perfectly to @layer:

@layer generic, elements, objects, components, utilities;

@layer generic { *, *::before, *::after { box-sizing: border-box; } }
@layer elements { body { font-family: system-ui; } }
@layer objects { .o-grid { display: grid; } }
@layer components { .c-card { padding: 1.5rem; } }
@layer utilities { .u-hidden { display: none; } }

With layers, you don't even need !important on utilities — the layer order handles priority.

CUBE CSS (Composition Utility Block Exception)

CUBE CSS embraces the cascade instead of fighting it:

/* Composition: Skeletal layout rules */
.sidebar-layout {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: var(--space-md);
}

/* Utility: Single-purpose, token-based */
.text-center { text-align: center; }
.flow > * + * { margin-block-start: var(--flow-space, 1em); }

/* Block: Component with scoped custom properties */
.card {
  --card-padding: var(--space-md);
  --card-radius: var(--radius-lg);
  padding: var(--card-padding);
  border-radius: var(--card-radius);
}

/* Exception: Variation via data attributes */
.card[data-variant="featured"] {
  --card-padding: var(--space-lg);
  border: 2px solid var(--color-accent);
}

CUBE Strengths

  • Works with the cascade, not against it
  • Custom properties for theming and variants
  • Less class name bloat than BEM
  • Data attributes for variants are self-documenting in HTML

CUBE + Modern CSS

/* Exception via :has() — no extra classes needed */
.card:has(img) { --card-padding: 0; }
.card:has([data-status="published"]) { opacity: 1; }

/* Composition via container queries */
.card-container { container: card / inline-size; }
@container card (min-width: 400px) {
  .card { flex-direction: row; }
}
Choosing a methodology by team context

Small team (1-3 devs), simple site: CUBE CSS — lightweight, embraces modern CSS, less boilerplate.

Medium team (4-10 devs), component-heavy app: BEM + ITCSS — explicit naming prevents collisions, layers manage specificity. Consider CSS Modules to enforce scope.

Large team (10+ devs), design system: ITCSS + Cascade Layers + CSS Modules — layer architecture for priority, modules for scope enforcement, tokens for consistency.

Any team with Tailwind: The methodology is Tailwind's utility-first approach, with cascade layers containing the reset and base styles.

Quiz
In CUBE CSS, how are component variations (like a 'featured' card) typically expressed?
Execution Trace
Reset layer
Generic styles: box-sizing, margin reset
Lowest specificity, lowest layer priority
Base layer
Element defaults: body, headings, links
Bare element selectors
Component layer
BEM classes: .card, .card__title, .card--featured
Flat (0,1,0) specificity, scoped by naming
Utility layer
.text-center, .hidden — highest layer priority
Always wins over components, no !important needed with layers
Unlayered
Emergency overrides, page-specific fixes
Only for truly exceptional situations
What developers doWhat they should do
Deeply nesting BEM: .block__element__subelement__child
BEM elements don't mirror DOM nesting. .card__icon is correct even if the icon is inside card__header in the DOM.
BEM only has one level of element: .block__element. Flatten the structure.
Using ITCSS layers as just file organization without enforcing specificity rules
The value of ITCSS is the specificity gradient. Without it, it's just file folders.
Each ITCSS layer should have progressively higher specificity — enforce this in code review
Mixing methodologies without a clear strategy
BEM naming with CUBE exceptions with random utilities creates confusion worse than no methodology
Pick one primary methodology and use others as supplementary patterns with clear boundaries
Applying BEM to every element including layout containers
Layout patterns (.grid, .stack, .sidebar-layout) are reusable across components and don't belong to any single block.
Use BEM for components. Use layout/composition classes separately (like CUBE's Composition layer).
Quiz
In BEM, what is the correct class name for a title inside a card's header?
Quiz
In ITCSS mapped to @layer, which layer should utility classes like .text-center be in?
Key Rules
  1. 1BEM uses flat specificity: .block, .block__element, .block--modifier — never nest beyond one level
  2. 2ITCSS organizes CSS from broad/low-specificity to narrow/high-specificity — maps directly to @layer
  3. 3CUBE CSS works with the cascade: composition for layout, utilities for single-purpose, blocks for components
  4. 4Choose methodology based on team size and tooling — no methodology is universally correct
  5. 5Cascade layers can enforce ITCSS-style priority without relying on specificity management