Every new project starts the same way: someone opens a blank repo and immediately runs npm install tailwind — or worse, pulls in a component library before a single screen exists.

Lines of CSS on a monitor

Boring is a feature

Plain CSS has gotten good. Custom properties, @layer, container queries, and :has() cover most layout problems without a build step. The cost of a framework is not the install — it’s the mental model you inherit.

“Simplicity is prerequisite for reliability.” — Edsger Dijkstra

That quote applies to styling too. A stylesheet you can read top-to-bottom beats a toolchain you need a cheat sheet to debug.

A starter that scales

Before you reach for utilities, try a token file and three layout primitives:

css
:root {
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-4: 1rem;
  --space-8: 2rem;
  --radius: 0.5rem;
  --foreground: oklch(0.2 0 0);
  --background: oklch(0.98 0 0);
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.sidebar {
  display: grid;
  grid-template-columns: 16rem 1fr;
  gap: var(--space-8);
}

Nothing exotic — just variables and composition. Most landing pages never need more.

When to add a framework

Add one when you have evidence, not enthusiasm:

  1. You are repeating the same utility patterns across dozens of files
    • Same flex/grid snippets copied into five components
    • Spacing values drifting (12px here, 0.75rem there)
    • New hires asking “where do I put styles?”
  2. Your team cannot agree on naming conventions
    • BEM vs utilities vs CSS modules vs “whatever works”
    • PRs blocked on style nits instead of behavior
  3. Design tokens need to propagate across multiple apps
    • Web + email + native share a palette
    • Brand updates require a single source of truth

Until then, a single global.css with a handful of variables will carry you further than you expect.

The payoff

Boring CSS is debuggable in DevTools, portable between projects, and never breaks on a major version bump. Sometimes the best abstraction is no abstraction at all.

bash
# No build step required
open index.html
# Edit globals.css, refresh — done

If your entire styling story fits in one file you can grep, you’ve won.