Writing CSS that ages well

Nine habits that keep a stylesheet readable five years and three redesigns later — and the one I had to unlearn.

Illustration of code brackets on a dark grid

The first stylesheet I was proud of lasted eleven months. By the second redesign it had become a museum of !important declarations, each one a small fossil of a deadline. This essay is about everything I’ve done differently since.

None of these ideas are new. What’s changed is that modern CSS finally makes them easy: custom properties, cascade layers, :where() and container queries mean the language now rewards restraint instead of punishing it.

1. Start with tokens, not components

Before writing a single selector I define the handful of decisions the whole site will lean on: colours, type scale, spacing and radius. Everything else refers to those names, never to raw values.

tokens.css
:root {
  --color-ink: #1c1b19;
  --color-paper: #fffaf2;
  --color-accent: #c2542f;
  --space-s: clamp(.75rem, 1vw + .5rem, 1rem);
  --radius: 10px;
}

When the brand colour changes — and it will — you edit one line. When dark mode arrives, you redefine the same names inside a media query and nothing else has to know.

2. Keep specificity flat

Specificity is debt. Every ID, every nested selector, every .page .sidebar .card h3 is a loan you’ll repay the day someone needs to override it. I aim for a single class per rule, and wrap anything more complex in :where() so it costs nothing.

base.css
/* zero specificity: easy to override later */
:where(.prose) :where(h2, h3) {
  margin-block: 2em .5em;
  text-wrap: balance;
}

3. Name for purpose, not appearance

A class called .blue-box is a promise you can’t keep. Six months later the box is green, and now the name is lying to everyone who reads it. Names like .callout or .price-tag describe a job, and jobs change far less often than colours.

  • Describe what it is, not what it looks like.
  • Prefer one clear word over a clever abbreviation.
  • Use modifiers sparingly: .callout.is-warning beats five new components.
Stacked paper cards representing layered components
Components stack like paper — each layer should only know about the one beneath it.

4. Trust the cascade

For years I fought the “C” in CSS, scoping every style so tightly that nothing could inherit. It felt safe. It was actually just verbose. Setting sensible defaults on body and letting typography flow down removed a third of my rules.

The cascade isn’t a bug to be contained. It’s the feature that lets a small stylesheet do a lot of work.

Cascade layers make this even calmer: put resets, base styles, components and utilities in declared layers, and the order of your files stops mattering.

main.css
@layer reset, base, components, utilities;

@import url("reset.css") layer(reset);
@import url("components.css") layer(components);

5. Delete with confidence

A stylesheet ages well when removing code is boring. Co-locate styles with the markup they belong to, keep one component per file, and add a comment at the top of anything that’s safe to delete when its feature goes away.

Try this: once a quarter, search your CSS for selectors that match nothing on the live site. The first time I did it, 22% of the file went in the bin.

The other four habits — logical properties, fluid type, container queries and a humble utility layer — deserve their own essay. That’s coming next month. Until then: write less, name honestly, and let the cascade carry the weight.

— Iris