> tailwind css — utility-first styling done right

// january 10, 2021

Tailwind CSS — Utility-First Styling Done Right

Tailwind CSS — Utility-First Styling Done Right

Tailwind CSS v2 dropped a month ago, and after migrating two projects I'm ready to share my verdict. When I first encountered Tailwind, my reaction was the same as most developers: "Inline styles with extra steps?" Now I can't imagine building a frontend without it.

The Utility-First Philosophy

Traditional CSS methodologies like BEM encourage semantic class names: .card__title--highlighted. Tailwind flips this entirely — instead of naming what something is, you describe what it looks like:

<h3 class="text-sm font-bold text-white mb-2">Post Title</h3>

This feels wrong until you realize the benefits:

  • No naming fatigue — you never waste time deciding between .card-header and .card-title
  • No dead CSS — utilities are only generated when used, thanks to PurgeCSS (now built into Tailwind)
  • Consistent design — spacing, colors, and typography are constrained to a design system defined in tailwind.config.js

What's New in v2

Tailwind v2 brought significant upgrades:

  • Dark modedark: variant for easy theme support
  • Extended color palette — 22 colors with 10 shades each
  • @apply in components — use utilities inside CSS when component extraction makes sense
  • Ring utilitiesring-2 ring-accent for focus indicators without box-shadow hacks

The Design System Advantage

Tailwind's configuration file is where it truly shines. Instead of arbitrary values scattered across stylesheets, your entire design language is centralized:

module.exports = {
  theme: {
    extend: {
      colors: {
        accent: '#10B981',
        surface: '#1F1F1F',
        border: '#2a2a2a',
      },
      fontFamily: {
        mono: ['"JetBrains Mono"', 'monospace'],
      }
    }
  }
}

Every text-accent, bg-surface, and border-border in your templates maps to this single source of truth. Change a color in the config, and it propagates everywhere.

Responsive Design Without Media Queries

Tailwind's responsive prefixes replace manual media queries:

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">

One line replaces what would traditionally require a separate stylesheet block with breakpoint definitions. The mobile-first approach is baked in.

Criticism and Counterarguments

The most common criticism — "it's just inline styles" — misses the point. Inline styles can't do responsive design, hover states, focus rings, dark mode, or animations. Tailwind can:

<button class="border border-accent text-accent px-6 py-3
               hover:bg-accent/10 transition-colors
               focus:outline-none focus:ring-2 focus:ring-accent/50">
  Submit
</button>

The other criticism — "HTML gets messy" — is valid for raw HTML. But in component-based frameworks like Svelte, each component's template is small and self-contained. Utility classes in a 20-line Svelte component are perfectly readable.

Practical Tips

After a month of daily Tailwind v2 usage, my advice:

  1. Use @apply sparingly — it defeats the purpose of utility-first
  2. Customize the config — default Tailwind looks generic. A custom palette and font stack make all the difference
  3. Learn the spacing scale — once you internalize that p-4 is 1rem and p-6 is 1.5rem, you'll design faster than ever
  4. Combine with component frameworks — Tailwind pairs best with Svelte, React, or Vue where you extract reusable components, not reusable CSS classes

Tailwind CSS changed how I approach frontend development. The constraint of a design system, the speed of utility classes, and the performance of tree-shaken CSS make it an essential tool.