Small projects forgive messy CSS; large ones do not. The difference between CSS that scales and CSS that collapses into specificity wars is architecture — deliberate decisions about organisation, specificity and the cascade. This lesson is where you start thinking like an engineer.
The core problem: specificity creep
As projects grow, developers fight to override each other by raising specificity, until everything is <code>#app .list li a.active</code> or worse, <code>!important</code>. The cure is to keep specificity low and flat from the start so any rule can override any other by source order.
Organising files by layer
A common approach (inspired by ITCSS) orders CSS from generic to specific: settings (variables), base (resets, element defaults), layout, components, then utilities. Loading them in that order means specificity naturally rises in step with source order.
| Layer | Contains |
|---|---|
| Settings | Variables, tokens |
| Base | Reset, element defaults |
| Layout | Grids, page structure |
| Components | Buttons, cards, nav |
| Utilities | Single-purpose helpers |
Cascade layers (@layer)
Modern CSS makes this explicit with <code>@layer</code>. You declare an order, and layers earlier in the order always lose to later ones regardless of specificity. This ends specificity wars: a low-specificity utility in a later layer can reliably override a complex component selector.
@layer base, components, utilities;
@layer components {
.card a { color: navy; }
}
@layer utilities {
.text-red { color: red; } /* wins, despite lower specificity */
}Keep specificity flat
#main .sidebar ul li a { … }.sidebar-link { … }Nesting selectors deeply to "scope" styles, which inflates specificity and makes overrides painful. Prefer a single, well-named class.
Style with single classes, organise files from generic to specific, and use @layer to control the cascade explicitly. Flat, low specificity is the foundation of CSS that scales to thousands of lines.
Refactor a deeply-nested selector like "#app .list li a.active" into a single class. Then sketch a @layer order (base, components, utilities) and place a utility class that overrides a component.
1. What causes CSS to become unmaintainable at scale?
2. What does @layer control?
3. Best way to keep specificity low?
- Architecture prevents specificity wars as projects grow.
- Organise CSS from generic to specific (settings → base → layout → components → utilities).
- @layer controls the cascade explicitly, ending specificity battles.
- Keep specificity flat with single, well-named classes.