Customization and debugging

Know when to write CSS

Decide when utilities keep a component clear and when a semantic class, custom property, or ordinary stylesheet is easier to maintain.

Tailwind doesn’t forbid CSS. Utilities are a tool, and the goal is a design that’s clear and easy to change. Sometimes plain CSS gets you there faster.

Keep utilities inline when…

The element owns a small, explicit set of decisions: some spacing, a color, a responsive change, a hover state. That’s the sweet spot. You read the markup and you know exactly what it looks like.

When markup and behavior repeat, extract a framework component. A <Button tone="danger"> in React, Vue, or Astro can still be full of utilities inside. The caller gets a semantic prop, and the component owns the classes.

Write ordinary CSS when…

CSS itself is the clearer abstraction:

  • rich prose generated by Markdown or a CMS, where you can’t add classes to every element
  • third-party markup you don’t control
  • complex selectors or pseudo-elements like ::before
  • keyframes and coordinated animation
  • a semantic style shared outside the template system
  • browser behavior with no useful built-in utility

For example, styling article headings:

.article-content h2 {
  margin-block-start: 2.5rem;
  font-size: var(--text-2xl);
}

Notice var(--text-2xl). The CSS still reads from Tailwind’s theme, so it stays consistent with the utilities. And this one rule is easier to audit than [&_h2]:mt-10 [&_h2]:text-2xl repeated on every article container.

The three extension points

Use @utility for a small reusable CSS behavior that should work with Tailwind variants. Use @theme for values that should expand the token-driven API, like a new color or font. Use plain custom properties in :root for runtime values that don’t need utilities. Each has a job. Don’t use @utility for a color or @theme for a one-off behavior.

Two traps

Don’t extract a class just because the class list is long. A card with fifteen unique visual decisions is clearer in one component file than split between the markup and a .card selector in another file. Extract when there’s real reuse or a semantic boundary, not because the line wraps.

And don’t copy the same long class string around either. In a component framework, a component is usually a better abstraction than a CSS class, because it keeps markup, accessibility, and behavior together with the styles.

Use the simplest layer that makes the design clear. Tailwind and CSS are not competing. They work together in every project I build.

Try this: pick five styles in your project and classify each as inline utility, component, custom utility, theme token, or ordinary CSS. Write one sentence for each choice, then remove one abstraction that adds indirection without any reuse.

Lesson completed