Web Engineering

CSS gets better when it has fewer opinions

6 min read

CSS gets better when it has fewer opinions.

That sounds backwards if the site feels under-designed. The instinct is to add more style: another utility class, another component variant, another special margin, another clever hover state, another color that solves the current page.

Sometimes that is the right move. More often, the stylesheet is not missing opinions. It has too many small ones.

One card has a custom radius. One page has a special heading size. One component has its own shadow. One link decides to be clever. One layout invents a new spacing scale because the existing one was almost right. None of those choices feels expensive when it is made. Together they turn the site into a negotiation.

The CSS starts asking the next change to remember every previous exception.

defaults do more work than variants

A good default is worth more than five variants.

Defaults are the choices the site makes when nobody is paying attention: body background, text rendering, heading wrapping, paragraph rhythm, focus styles, link behavior, code font, spacing, and color tokens. If those are solid, most pages can stay quiet.

This site’s global CSS has a few defaults I like:

h1,
h2,
h3 {
  text-wrap: balance;
}

p,
li {
  text-wrap: pretty;
}

code,
kbd,
samp,
pre,
.type-mono {
  font-family: var(--font-mono);
  font-variant-ligatures: none;
}

Those rules do not announce themselves. They make many pages better without each page opting in.

That is the kind of CSS opinion I trust: broad enough to reduce local decisions, small enough that it does not trap the design.

special cases should feel expensive

Special cases are not forbidden. They should feel expensive enough to justify themselves.

A homepage profile treatment can have custom animation and layered visual behavior. That is a place where personality matters. A repeated blog-card layout probably should not invent its own local universe. A button should not need a new radius because this one page feels slightly different. A nav link should not pick a new focus treatment because the existing one was boring.

The check I use is simple: will I want this behavior again?

If yes, it might belong in the system. If no, it might still be fine, but I want the CSS to make the exception obvious. A one-off should look like a one-off when I come back later.

The problem is not custom CSS. The problem is accidental precedent.

tokens should name decisions, not moods

CSS variables are useful when they name stable decisions.

They get messy when they name vibes.

--surface-wash is a useful kind of token because it describes a role. --profile-orbit-blue is specific because it belongs to a particular visual system. Those names tell me where the value is supposed to be used.

The weak version is a theme full of tokens like --cool-blue, --soft-glow, --nice-shadow, and --modern-card. Those names do not carry decisions. They carry mood, and mood rots quickly.

A token should help the next person answer:

  • what role does this value play?
  • where is it allowed?
  • what breaks if it changes?
  • is it global or component-specific?

If the token cannot answer those questions, a raw value might be more honest until the pattern repeats.

fewer layout dialects

Layout drift is where CSS starts to feel haunted.

One page uses flex with gap. Another uses grid with local margins. Another uses negative space because a hero needed to overlap. Another wraps content in two containers because one component was too wide. Again, each decision can be reasonable. The drift is the problem.

I like layout systems that have a small vocabulary:

  • page container
  • prose container
  • dense toolbar
  • repeated item grid
  • split layout when the content actually needs it
  • full-width band for sections that need contrast

When those patterns are consistent, new pages become easier. The author thinks about content instead of inventing a layout grammar.

This matters for AI-generated UI too. A model will happily create a new layout dialect for every prompt unless the system gives it fewer legal moves.

component css should protect the component

Component-specific CSS should protect the component’s contract.

A nav link needs hover, active, and focus behavior. A scrollbar helper needs cross-browser details. A profile visual can own its animation and internal variables. Those are good reasons for component CSS because the behavior belongs to the component.

What I try to avoid is component CSS that compensates for an unclear system.

If every component needs its own heading sizes, spacing overrides, link colors, and dark-mode patches, the global layer is not doing enough or the components are being asked to do too many different jobs.

The component should mostly answer: what makes this thing itself?

It should not have to restate the whole site.

dark mode exposes weak opinions

Dark mode is a good test of whether the CSS has too many local decisions.

If colors are role-based, dark mode is manageable. If colors are scattered through page-specific classes, every adjustment becomes a search party. The site may still work visually, but future changes get slower.

The goal is not to abstract every color. That turns CSS into paperwork. The goal is to keep repeated roles named and keep one-off choices contained.

Dark mode also catches decorative excess. Shadows, borders, glows, and low-contrast text that looked fine in light mode can become muddy quickly. A restrained system usually survives theme changes better because fewer pieces are competing for attention.

boring css is easier to trust

The CSS I trust most is usually boring to read.

It has a few theme decisions. It has a few component contracts. It avoids clever selectors unless the behavior earns them. It uses modern features where they make the code simpler, not to prove the stylesheet is current. It treats one-off visual moments as exceptions instead of letting them leak into the whole site.

That kind of CSS does not make a site bland. It gives the distinctive parts room to matter.

The profile treatment can be expressive because every card is not trying to be expressive. The navigation can have a recognizable interaction because links are not improvising everywhere else. The writing can carry the page because the layout is not arguing with it.

Fewer opinions does not mean no taste.

It means the stylesheet stops narrating every tiny choice and starts making the common path predictable.

Jeremy London

About Jeremy London

Engineering leader and builder in Denver. I write about AI platforms, agents, security, reliability, homelab infrastructure, and the parts of engineering work that have to survive production.