CSS Architecture Methodologies
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.
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__titleappears nowhere else
BEM Weaknesses
- Verbose class names:
.navigation__item--activeis 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.
| What developers do | What 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). |
- 1BEM uses flat specificity: .block, .block__element, .block--modifier — never nest beyond one level
- 2ITCSS organizes CSS from broad/low-specificity to narrow/high-specificity — maps directly to @layer
- 3CUBE CSS works with the cascade: composition for layout, utilities for single-purpose, blocks for components
- 4Choose methodology based on team size and tooling — no methodology is universally correct
- 5Cascade layers can enforce ITCSS-style priority without relying on specificity management