08 — Modern CSS — Stop Naming Things, Start Composing Intent
"A pile of unrelated tools" was my modern-CSS read, and it missed the single shift underneath. The shift: The shift isn't a framework. It's giving up on naming classes and writing CSS rules by hand, and instead composing intent at the element — then letting a build step generate only the CSS the page actually uses [1].
The whole story is one tradeoff, worth seeing up front:
Once I saw it as one shift, the bag of tools fell into a single lineage: each generation tried to kill the same two pains — the global namespace of class names, and the dead CSS that ships when rules are written by hand.
The pains that drove everything
Hand-written CSS has two structural problems, and every modern approach is a different answer to them [1][2].
- The global namespace. A class name like .title lives in one flat global scope. Two developers on the same project both write .title, and whichever stylesheet loads last wins. Naming conventions (BEM — block__element--modifier) were the first answer: discipline the humans so collisions don't happen. It works, but it pushes the cost into my head — I spend real effort inventing names that won't clash and then keeping them consistent.
- Dead CSS. I write a rule for .profile-card, ship it, then six months later delete the markup. The rule stays in the stylesheet forever because nobody dares remove it — maybe something else still uses it. Real codebases ship kilobytes of CSS that matches nothing. The selector-to-element mapping lives only in a human's memory, and it rots.
Every modern approach is an attempt to delete one or both of those costs.
Responsive: media queries and mobile-first
Before the tooling, the language itself leveled up. Media queries let a stylesheet branch on conditions of the viewport — @media (min-width: 768px) { ... } applies only when the screen is at least that wide [3]. The mental move that goes with them is mobile-first: write the small-screen styles as the base, then layer min-width queries for larger screens. Each breakpoint only adds, never undoes, so the cascade stays predictable. (The opposite — desktop-first with max-width queries undoing desktop rules — produces a fighting-the-cascade feeling I learned to avoid.)
That's a language feature, not a framework — it composes with any approach below. It belongs here because responsive design is what made hand-written CSS really hurt: every component suddenly needs three or four variants, the stylesheet balloons, and dead CSS gets worse. Tooling became worth the cost precisely because responsive multiplied the CSS we write.
CSS-in-JS: scope by generating the names
The first tooling answer was CSS-in-JS. Instead of me naming classes, the library generates a unique class name at build (or runtime) for each style and scopes it to the component. The global namespace disappears by construction — two .titles become .title-3kf9a and .title-7b2xc, so they can't collide [2]. Dead CSS gets solved too: styles live inside the component file, so when I delete the component, its styles go with it. The cost is runtime overhead (older libraries injected styles on render) or build complexity (newer ones extract at compile time), plus tight coupling to the JS runtime.
Utility-first: scope by not naming at all
Tailwind took the opposite route. Instead of generating unique names for hand-written rules, don't name things at all [4]. Compose the element's appearance directly from small, single-purpose utility classes — p-4 for padding, text-lg for size, flex for layout — each a thin wrapper over one CSS declaration. The element's intent lives on the element. There's no stylesheet to hunt through, and no name to collide.
The killer feature, though, isn't the utilities — it's the build step. Tailwind scans the markup, finds only the utility classes actually present, and generates a stylesheet containing exactly those declarations and nothing else [4][5]. Dead CSS is structurally impossible: a utility that isn't in the markup isn't in the output. The shipped CSS for a whole app is typically a few kilobytes.
Responsive design falls out for free in the same model. Because utilities are mobile-first by default, the breakpoint variants are just prefixes: md:flex means "flex from the md breakpoint up." The breakpoint cascade reads left to right on the element (block md:flex p-4 lg:p-8) instead of being scattered across a stylesheet. I stopped context-switching between markup and CSS; the layout decisions live where the element lives.
The honest tradeoff
This isn't free, and the reason it took me a while to adopt is visible in the markup: class lists get long and ugly. <div class="flex items-center gap-4 rounded-lg bg-white p-4 text-sm shadow md:flex-row md:p-6"> is real, and it's less "readable" in the traditional sense than a <div class="card"> with the rules hidden in a stylesheet.
The reframing: readable where? The hand-written stylesheet moves complexity into a separate file I have to keep in sync. Utility-first keeps it in the markup, so there's only one place to look — no jumping to find what .card does. I traded stylesheet-readability for markup-readability and zero dead CSS. For CSS that's mostly layout, spacing, color, and type — which is most of what I write — that's a trade I'll make every time [5].
Where I reach past it: genuinely complex styling with lots of state, animation, or theming that doesn't map to utilities. The two coexist — utility-first for layout and spacing, escape hatches (@apply, or a plain CSS module) for the rare cases that need them.
How I use this
The habit these notes left me with is a default I didn't have: reach for utility-first unless there's a specific reason not to. Most of what I style — spacing, layout, responsive, color, type — is exactly what utilities model well, and the purge keeps shipped CSS as small as it can be. The mental load I used to spend inventing and consistent-keeping class names is just gone. That's the real win — not the syntax, but the naming problem I no longer have to solve.
References
[1] Stephanie Eckles, "Modern CSS Solutions for Old CSS Problems," moderncss.dev, 2024. [Online]. Available: https://moderncss.dev/
[2] Google, "Learn CSS," web.dev, 2024. [Online]. Available: https://web.dev/learn/css
[3] Mozilla, "Using media queries," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries
[4] Tailwind Labs, "Tailwind CSS — Rapidly build modern websites without ever leaving your HTML," 2024. [Online]. Available: https://tailwindcss.com/
[5] Tailwind Labs, "Optimizing for production," Tailwind CSS Documentation, 2024. [Online]. Available: https://tailwindcss.com/docs/optimizing-for-production
Knowledge check · Question 1 of 5
What are the two structural pains of hand-written CSS that every modern approach tries to solve?
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!