Skip to content
Finder Theme — ~/Blog / Engineering / Tailwind is my design system
← All posts
Article • tailwind-is-my-design-system.md • 37 lines
Engineering 3 min

Tailwind is my design system

How constraints became creative freedom. I stopped fighting CSS and started composing with it.

chris

26 articles

For years I wrote CSS the way I was taught: semantic class names, a separate stylesheet, careful cascade management. It worked until it didn’t — usually around the point where three components needed almost-but-not-quite the same spacing.

The constraint is the point

A utility scale gives you eight spacing values instead of infinity. That sounds limiting until you notice that every layout you have ever admired used about eight spacing values.

The scale removes a decision you were never making well anyway. What remains is composition, which is the part that actually needs judgement.

Where it stops working

Utilities are a poor fit for anything that has to be computed at runtime. A progress bar whose width comes from a scroll position cannot be a class, because the compiler only ever sees classes that exist literally in your source.

That is not a flaw. It is a boundary, and boundaries are useful: they tell you exactly where an inline style is the correct answer rather than a lazy one.

What a design system actually has to do

A design system has three jobs: make the common case fast, make the inconsistent case obvious, and make the exceptional case possible. Most systems nail the first, ignore the second, and actively obstruct the third.

Component libraries obstruct the third because the escape hatch is always a prop that nobody wants to add. Utility scales handle it naturally: the exceptional case is just a value the scale does not contain, written inline, visible in the diff, and impossible to mistake for the norm.

Inconsistency should be legible

This is the underrated part. When spacing lives in a stylesheet, drift is invisible — you find it months later with a screenshot comparison. When spacing lives in the markup, the moment someone writes an arbitrary value it shows up in review, next to the person who wrote it, at the time they wrote it.

The objections, honestly

The markup is noisy. That is true and it is the actual cost. I have made peace with it because I read markup in an editor with syntax highlighting and I read stylesheets while holding two files in my head, and the first turns out to be easier despite looking worse.

It couples structure to presentation. Also true, and it matters if you genuinely reskin the same markup for different products. Almost nobody does. The theming most teams need is a token swap, which a utility scale handles fine.

It does not scale to large teams. I have not found this. What does not scale is a large team with no agreement about the scale, which is a governance problem that a stylesheet would not have solved either.

What I would tell myself three years ago

  • Configure the scale once, properly, before writing any components
  • Extract a component the third time you repeat a pattern, not the second
  • Never build a class name by concatenation — the compiler cannot see it
  • Put runtime-computed values in inline styles and stop feeling guilty about it
  • Read the generated stylesheet occasionally; it tells you what you actually use

The last one is the most useful habit. Compiled output is an honest summary of a codebase’s design decisions, and it takes about five minutes to read.

1 Comment

Threaded
Priya

The bit about runtime values being a boundary rather than a flaw is the clearest explanation of this I have read.

Leave a reply

* Required