Skip to main content
Back to Journal
CSSWeb Development

Tailwind CSS and the Case for Utility-First Styling

I will be honest: the first time I saw Tailwind CSS, I thought it was a joke. A div with fifteen class names that read like someone had a keyboard seizure? It looked like inline styles with extra steps. I had spent years learning BEM naming conventions, building component-scoped CSS Modules, and experimenting with styled-components. Tailwind seemed like a step backward.

Then I actually tried it on a project, and within a week I understood why people were so passionate about it. Three months later, I had converted every new project to Tailwind and was slowly migrating old ones. Here is what changed my mind.

How Utility-First Actually Works

The core idea of Tailwind is simple. Instead of writing custom CSS classes that describe what a thing is (.card-header, .sidebar-nav), you compose styles from small, single-purpose utility classes that describe what a thing looks like (text-lg, font-bold, p-4, bg-white).

A card component in Tailwind looks like this:

<div class="bg-white rounded-lg shadow-md p-6 max-w-sm">
  <h2 class="text-xl font-semibold text-gray-900 mb-2">Card Title</h2>
  <p class="text-gray-600 text-sm leading-relaxed">
    Card description goes here with some body text.
  </p>
  <button class="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors">
    Learn More
  </button>
</div>

Yes, the class lists are long. But look at what you get. Every style is visible right in the HTML. You do not need to jump to a separate CSS file to understand how an element is styled. You do not need to think of a class name. You do not need to worry about naming collisions. The styling is colocated with the markup, and that turns out to be a huge productivity win.

Configuration and Customization

Tailwind is not a rigid framework. The tailwind.config.js file lets you customize everything: colors, spacing scale, fonts, breakpoints, and more.

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          light: '#3fbaeb',
          DEFAULT: '#0fa9e6',
          dark: '#0c87b8'
        }
      },
      fontFamily: {
        display: ['Inter', 'sans-serif'],
        body: ['Roboto', 'sans-serif']
      },
      spacing: {
        '72': '18rem',
        '84': '21rem',
        '96': '24rem'
      }
    }
  }
};

Once configured, your custom values are available as utilities throughout the project. bg-brand, text-brand-dark, font-display, p-72. This is where Tailwind becomes a design system tool, not just a CSS utility library. Your configuration file is the single source of truth for your project's design tokens. Change a color in the config, and every component using that color updates. This is something BEM and vanilla CSS never gave you without preprocessor variables.

Responsive Design with Prefixes

Responsive design in Tailwind uses prefix modifiers that correspond to breakpoints. The default breakpoints are sm (640px), md (768px), lg (1024px), and xl (1280px). Tailwind is mobile-first, so unprefixed utilities apply to all screen sizes, and prefixed utilities apply at that breakpoint and above.

<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
  <div class="p-4 bg-white rounded shadow">Item 1</div>
  <div class="p-4 bg-white rounded shadow">Item 2</div>
  <div class="p-4 bg-white rounded shadow">Item 3</div>
</div>

This grid is single-column on mobile, two columns on small screens, and three columns on large screens. All expressed inline, no media queries to write. The mental model is clear: start with the mobile layout, then layer on changes at larger breakpoints using the prefixes.

Compare this to writing media queries in regular CSS:

/* Traditional CSS */
.grid-container {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}
@media (min-width: 640px) {
  .grid-container { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .grid-container { grid-template-columns: repeat(3, 1fr); }
}

The Tailwind version is more readable and requires no context switching between files.

PurgeCSS for Production

The biggest objection to Tailwind is file size. The full utility stylesheet is massive, over 3MB uncompressed. Shipping that to production would be absurd. But you never ship the full stylesheet.

Tailwind integrates with PurgeCSS to scan your templates and remove every utility class that is not actually used. In your tailwind.config.js:

module.exports = {
  purge: [
    './src/**/*.html',
    './src/**/*.jsx',
    './src/**/*.tsx'
  ],
  theme: { /* ... */ }
};

When you build for production, PurgeCSS scans those files, finds every class name string, and strips everything else from the output CSS. A typical Tailwind production build is 5 to 15KB gzipped. That is smaller than most hand-written CSS files for projects of similar complexity. The development experience uses the full utility set. Production ships only what you use.

Extracting Components with @apply

For components that repeat across your codebase, Tailwind offers the @apply directive to extract utility patterns into custom classes:

/* styles.css */
.btn-primary {
  @apply bg-blue-500 text-white font-semibold py-2 px-4 rounded;
  @apply hover:bg-blue-600 transition-colors;
}

.card {
  @apply bg-white rounded-lg shadow-md p-6;
}

Now you can use class="btn-primary" instead of repeating the full utility list. I use @apply sparingly. For most components in React, the component itself is the abstraction. You write the utility classes once in the component definition, and reuse the component. @apply is most useful for elements that appear in raw HTML or in a CMS where you cannot use component abstractions.

Dark Mode Support

Tailwind 2.0 added first-class dark mode support with the dark: prefix:

<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 p-6 rounded-lg">
  <h2 class="text-xl font-bold">Dark mode ready</h2>
  <p class="text-gray-600 dark:text-gray-400">This adapts automatically.</p>
</div>

You can configure dark mode to use the prefers-color-scheme media query or a manual class toggle. Every utility in Tailwind can be prefixed with dark: to apply conditionally. Adding dark mode to an existing Tailwind project is mostly a matter of going through your components and adding the dark variants, which is tedious but straightforward. No separate stylesheet, no CSS variable juggling.

Comparison with BEM, CSS Modules, and styled-components

I have used all of these, and each has real strengths. BEM gives you structured naming at the cost of verbosity and zero protection against naming collisions in large teams. CSS Modules solve the scoping problem but still require writing traditional CSS in separate files. styled-components colocate styles with components but introduce runtime overhead and a different mental model for conditional styles.

Tailwind's advantages: no naming decisions, no separate files, no runtime cost, built-in responsive and state variants, a consistent design system from the config file, and tiny production bundles. The main drawback is the learning curve for the utility class names, but after a week of use with the Tailwind CSS IntelliSense VS Code extension providing autocomplete, the class names become second nature.

Why I Converted

The moment that convinced me was a Thursday afternoon refactor. I needed to change the layout of a card component from vertical to horizontal on larger screens. In my old CSS Modules setup, that meant editing the CSS file, adding a media query, adjusting flexbox properties, and testing. In Tailwind, I changed flex-col to flex-col lg:flex-row and added lg:w-1/2 to the child elements. Done in thirty seconds, readable at a glance, no file switching.

Tailwind is not perfect. The long class lists can be overwhelming at first. Some developers strongly prefer the separation of concerns that external CSS provides. And for truly complex animations or layouts, you still need custom CSS. But for 90% of the styling work in a typical web application, Tailwind is faster, more maintainable, and more predictable than any approach I have used before. I went in as a skeptic and came out a convert.

tailwindcssutility-firstresponsive-designdesign-system