Steve Kinney

Utility-First CSS

Behold, the <button>. After Tailwind strips most of the built-in browser styles as part of it’s Preflight. In fact, it barely looks like a button.

<button>Button</button>

We can begin to add a little more to it by using utility classes.

Utility-first CSS prioritizes small, single-purpose classes applied directly to HTML elements instead of semantic classes or IDs. Each utility class does one thing—sets margin, padding, text alignment, or color.

<button class="bg-blue-600 px-3 py-2 text-white">Button</button>

Tailwind pushes the utility-first philosophy further by embracing modern CSS features—cascade layers, OKLCH colors, container queries, and native custom properties—all without leaving HTML.

Advantages

  1. Rapid Prototyping: Style elements directly in HTML without switching files.
  2. Reduced Side Effects: Classes scoped to individual elements prevent styling conflicts.
  3. Reusability: Single-purpose classes work across the entire application.
  4. Maintainability: Effects of adding/removing classes are predictable and visible in markup.
  5. Easy to Extend: Frameworks provide customization options for consistent design systems.

Criticisms

  1. Verbose HTML: Numerous class names can clutter markup.
  2. Learning Curve: Requires familiarity with utility class names.
  3. Lack of Semantics: Utility classes don’t convey element meaning (mitigated by combining with semantic classes).

Why It Works

  • Predictable cascade: Utilities declared last in the cascade override component and base styles without !important.
  • Design-token synergy: Tokens in @theme (--color-primary-500) are available in every utility.
  • Rapid iteration: Oxide rebuilds styles in milliseconds.

Best Practices

  • Compose in HTML first: Reach for custom CSS only when utilities can’t express the idea clearly.
  • Extract repeating patterns: Wrap utilities appearing 3+ times in components or @apply.
  • Use @apply sparingly: Selectors in @layer components/base don’t receive variants. For variant-aware helpers, use @utility.
  • Declare tokens in @theme: Use CSS variables for spacing, colors, fonts instead of arbitrary values (p-[123px]).

Managing Long Class Lists

Keep long class lists readable:

  1. Logical grouping: Order by layout → spacing → typography → color → motion.
  2. Automatic sorting: Use official Tailwind CSS IntelliSense extension or the Prettier Tailwind plugin to enforce consistent ordering.
  3. Component extraction: Pull verbose patterns into reusable components to keep markup tidy.

Advanced Variants & State

Tailwind 4 introduces powerful variant primitives:

VariantPurposeExample
md:Media query ≥ 768pxmd:grid-cols-3
hover:Interaction statehover:bg-primary-600
cq:Container querycq:gap-6
group-has-[.active]Style parent when it contains .activegroup-has-[.active]:bg-blue-50

Combine them for expressive rules like md:group-hover/cell:translate-y-1.

Performance Considerations

  • Oxide build step: Tailwind emits only referenced classes for minimal CSS bundles.
  • Avoid runtime JIT in prod: Compile-time is faster with no client-side overhead.
  • Mind variant explosion: Each variant combination adds bytes. Prune unused classes.

Knowing When to Write Custom CSS

Use custom CSS when:

  • Complex keyframe animations are needed
  • Algorithmic values required (e.g., trig‑based transforms).
  • Selector logic exceeds what utility variants offer.

Keep such rules small, place them in the correct cascade layer, and document why utilities weren’t sufficient.

Further Reading

  1. Tailwind CSS: Most popular, extensive utilities for layouts to theming.
  2. Tachyons: Fast-loading, readable, maintainable code.
  3. Basscss: Lightweight, extensible base utilities.

Last modified on .