Level 4, Real Data & Architecture, Chapter 18 of 24
Modern CSS Architecture
Why this matters
A stylesheet with ten rules is easy to manage. A stylesheet with a thousand rules, written without a plan, becomes a maze where changing one button's color accidentally breaks three other pages. As your projects grow past a single page, how you organize CSS starts to matter as much as the CSS itself.
This chapter gives you three tools professional teams rely on: custom properties (CSS variables) to centralize values like colors and spacing, a naming convention called BEM to keep class names predictable and collision-free, and a proper dark mode implementation that reacts to user preference. Together they turn a pile of styles into a system you can scale and reason about.
The lesson
CSS custom properties
You've been writing literal values in your CSS since chapter one: specific hex colors, specific pixel values for spacing. Custom properties let you name a value once and reuse it everywhere, similar to a variable in JavaScript. You define them with a -- prefix, typically on :root so they're available globally, and read them with var(--name). The real payoff comes when you need to change a value across an entire site, like a brand color, updating one line instead of hunting through every rule that used it. Custom properties can also be overridden inside specific selectors, letting a section of your page use a different value without any JavaScript at all.
1:root {2 --color-primary: #2563eb;3 --color-text: #1f2937;4 --space-md: 1rem;5 --radius: 8px;6}78.button {9 background: var(--color-primary);10 color: white;11 padding: var(--space-md);12 border-radius: var(--radius);13}BEM: a naming convention for sanity
As pages grow, it becomes easy to accidentally write two different rules that both target .title or .card, and have them fight each other in ways that are hard to trace. BEM (Block, Element, Modifier) is a naming convention that avoids this by encoding structure directly into class names. A Block is a standalone component, like card. An Element is a part of that block, written card__title. A Modifier is a variation, written card--featured. Every class name becomes self-descriptive and, crucially, flat: you rarely need to nest selectors deeply or rely on specificity tricks, because each class already says exactly what it styles.
1<div class="card card--featured">2 <h3 class="card__title">Featured Post</h3>3 <p class="card__excerpt">A short summary goes here.</p>4</div>56.card { border: 1px solid #e5e7eb; border-radius: var(--radius); padding: var(--space-md); }7.card--featured { border-color: var(--color-primary); }8.card__title { font-size: 1.25rem; margin: 0 0 0.5rem; }9.card__excerpt { color: #6b7280; }BEM is a convention, not a browser feature, so nothing enforces it automatically, but teams that adopt it consistently find their CSS much easier to navigate months later, because a class name alone tells you where in the markup it belongs and what it's for. It pairs especially well with custom properties: your BEM classes handle structure and layout, while custom properties handle the actual color and spacing values, so a full theme change never requires touching your component classes at all.
Implementing dark mode properly
A real dark mode isn't just swapping a background color, it's redefining your custom properties for a different context and letting every component that already uses var(--color-text) and similar variables update automatically. The prefers-color-scheme media query detects the user's operating system preference, while a class toggled by a button lets users override that preference manually. Combining both, with the manual choice saved to localStorage from the previous chapter, gives you the same dark mode experience found on most professional sites: respects the system by default, remembers an explicit choice.
1:root {2 --bg: #ffffff;3 --text: #1f2937;4}56@media (prefers-color-scheme: dark) {7 :root { --bg: #111827; --text: #f9fafb; }8}910.theme-dark {11 --bg: #111827;12 --text: #f9fafb;13}1415body {16 background: var(--bg);17 color: var(--text);18 transition: background 0.2s ease, color 0.2s ease;19}- Custom properties (
--name) centralize values and are read withvar(--name) - BEM structures class names as block, block__element, block--modifier
- Flat BEM classes reduce reliance on deep selector nesting and specificity fights
prefers-color-schemedetects the operating system's light/dark preference- A toggled class lets users override the system preference manually
- Pairing custom properties with dark mode avoids rewriting individual selectors
Try it yourself
Toggle dark mode and inspect how custom properties cascade through BEM-named components without rewriting any component styles.
Chapter boss project
Refactor a Messy Component Library
You'll be given (or should write) a small page with three or four repeated components, like cards, buttons, and alerts, using inconsistent class names and hardcoded colors sprinkled everywhere. Your job is to refactor it into a clean system.
Extract every repeated color, spacing, and radius value into custom properties on :root. Rename every class to follow BEM consistently, ensuring blocks, elements, and modifiers are clearly distinguished. Then implement a working dark mode toggle that updates the entire page by redefining custom properties, not by rewriting component rules.
By the end, changing your primary accent color or switching themes should require editing only your custom properties, never touching the component-level CSS rules themselves. That's the real test of whether your architecture works.
- List every repeated raw value (colors, spacing, radii) in your original CSS before renaming anything, then turn each into a custom property.
- Refactor one component fully (structure, BEM classes, variables) before moving to the next, rather than doing all the renaming across the whole file at once.
- For dark mode, define a `.theme-dark` class that only overrides custom property values, and toggle it on the body or root element.
- Double-check your work by changing one custom property value and confirming multiple components update together.
Level-up checklist
Tick these off once each one is true for you.
