Utility-First CSS

Tailwind CSS

Utility-first CSS lets you style directly in your markup with small, composable classes. Here is how it works, why it took over, and how to use it without making a mess.

intermediate14 min readUpdated Sep 15, 2026
card.html
html
<!-- card.html -->
<div class="rounded-2xl border border-white/10 bg-white/5 p-6
            hover:border-white/20 transition-colors">
  <h3 class="text-lg font-semibold text-white">Pro plan</h3>
  <p class="mt-2 text-sm text-white/60">Everything you need to ship.</p>
  <button class="mt-4 rounded-lg bg-emerald-500 px-4 py-2 text-sm
                 font-medium text-black hover:bg-emerald-400">
    Get started
  </button>
</div>
Approach
Utility-first
Output
Plain, static CSS
Runtime
None — build-time only
Config
CSS-first in v4
Variants
hover:, md:, dark:
Pairs with
Any framework

Why it matters

Why developers reach for Tailwind

No context switching

Style and markup live together, so you stop bouncing between an HTML file and a stylesheet to make one change.

A design system built in

Spacing, colour and type scales come from a shared theme, which keeps interfaces consistent by default.

Tiny production CSS

The compiler scans your templates and emits only the classes you actually use, so unused utilities cost nothing.

The big picture

The three ideas behind Tailwind

Tailwind is not just a pile of classes. It is a design system, a variant engine and a build step that only ships what you use.

Utilities

Composition

Small single-purpose classes such as flex, p-4 and text-sm that you compose to build any design.

Variants

Conditions

Prefixes like hover:, focus:, md: and dark: apply a utility only under a specific condition.

The build step

Compilation

A scanner finds the classes in your source and generates exactly the CSS you need.

Tailwind at a glance

The core building blocks

Layout utilities

flex, grid, gap, p-*, m-*, w-* and friends cover almost all layout work.

Responsive prefixes

sm:, md:, lg: and xl: apply styles from a breakpoint upward, mobile first.

State variants

hover:, focus:, active: and group-hover: style interactive states inline.

Dark mode

The dark: variant switches styles with a class or the system preference.

Arbitrary values

Square brackets let you use a one-off value like w-[37rem] without config.

Components with @apply

Extract repeated utilities into a class when a pattern is genuinely reused.

A short history

How Tailwind became the default

  1. 2017

    First release

    Tailwind introduces utility-first CSS to a skeptical audience used to semantic class names.

    17
  2. 2020

    Just-in-time engine

    The JIT compiler generates styles on demand, unlocking arbitrary values and faster builds.

    20
  3. 2021

    Tailwind v3

    First-class dark mode, container queries groundwork and a richer default palette.

    21
  4. 2024

    Tailwind v4

    A CSS-first configuration, a Rust-powered engine and dramatically faster builds.

    24
  5. Today

    The modern default

    Widely used across React, Vue, Svelte, Astro and plain HTML projects.

    Today

The complete guide

Tailwind CSS: Everything you need to know

What is Tailwind CSS?

Tailwind is a utility-first CSS framework. Instead of giving you ready-made components like .btn or .card, it gives you thousands of small, single-purpose classes — flex, p-4, text-sm, rounded-lg, hover:bg-blue-500 — that you compose directly in your HTML to build any design you want.

The idea sounds strange at first. Why write class="rounded-lg bg-blue-600 px-4 py-2" when you could write class="button"? The answer is that you stop inventing names, stop jumping between files, and stop maintaining a growing pile of custom CSS. The styling lives exactly where the element is defined, and a shared theme keeps everything consistent.

Utility-first in practice

A utility does one thing. p-4 sets padding. flex sets display. text-center aligns text. You combine them to build a component.

<!-- profile.html -->
<div class="flex items-center gap-4 rounded-xl border border-white/10 p-4">
  <img class="h-12 w-12 rounded-full" src="avatar.jpg" alt="" />
  <div>
    <p class="font-semibold text-white">Ada Lovelace</p>
    <p class="text-sm text-white/60">Engineer</p>
  </div>
</div>

Every value comes from a scale defined in your theme: spacing in 0.25rem steps, a curated colour palette, a type scale and more. That constraint is the point — it makes it hard to invent a one-off margin: 13px and easy to stay consistent.

Responsive design

Responsive variants apply a utility from a breakpoint upward, which is mobile-first by design.

<!-- responsive.html -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
  <div class="rounded-xl bg-white/5 p-4">One</div>
  <div class="rounded-xl bg-white/5 p-4">Two</div>
  <div class="rounded-xl bg-white/5 p-4">Three</div>
</div>

Read grid-cols-1 md:grid-cols-2 lg:grid-cols-3 as: one column by default, two columns from the md breakpoint, three from lg. You write the mobile layout first and layer on larger screens, which matches how CSS media queries actually cascade.

State and pseudo-class variants

Interactive states are just more prefixes. hover:, focus:, active:, disabled: and focus-visible: all work the same way.

<!-- button.html -->
<button class="rounded-lg bg-emerald-500 px-4 py-2 text-black transition
               hover:bg-emerald-400 focus-visible:outline
               focus-visible:outline-2 disabled:opacity-50">
  Submit
</button>

Tailwind also has group and peer variants for styling a parent based on a child’s state (or vice versa), which covers dropdowns, cards and form validation without any JavaScript.

<!-- group.html -->
<a href="#" class="group block rounded-xl p-4 hover:bg-white/5">
  <h3 class="font-semibold group-hover:text-emerald-400">Read more</h3>
  <span class="opacity-0 transition group-hover:opacity-100">→</span>
</a>

Dark mode

The dark: variant applies styles when dark mode is active. Depending on configuration it follows the system preference or a class on the root element, which makes a manual toggle easy.

<!-- theme.html -->
<body class="bg-white text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100">
  <article class="rounded-2xl bg-zinc-100 p-6 dark:bg-zinc-900">
    Content adapts to the theme.
  </article>
</body>

Because both themes are expressed on the same element, you never lose track of a dark-mode style in a separate stylesheet.

Arbitrary values and customisation

When the theme does not have exactly what you need, square brackets let you drop in a one-off value.

<!-- arbitrary.html -->
<div class="w-[37rem] bg-[#1f2937] text-[13px]">
  Arbitrary values
</div>

Use these sparingly — if a value repeats, add it to the theme instead. In Tailwind v4 the theme is defined in CSS with @theme, which keeps tokens in one place and makes them available as custom properties.

/* app.css */
@import "tailwindcss";

@theme {
  --color-brand: #14b8a6;
  --spacing-section: 6rem;
}

Extracting components

Long class lists are the main criticism of Tailwind. The right fix is almost always a component, not @apply.

// Button.jsx
export function Button({ children }) {
  return (
    <button className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-500">
      {children}
    </button>
  );
}

In a component framework, the utility list lives in one place and every usage stays consistent. When you genuinely cannot use a component — third-party markup, for example — @apply can extract a class:

/* components.css */
.btn {
  @apply rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white;
}

Reserve @apply for those cases. Overusing it rebuilds the custom-CSS architecture Tailwind avoids.

Tailwind vs Bootstrap

Both are CSS frameworks, but they solve different problems.

  • Tailwind gives you primitives. You build your own components, which means more control and a more distinctive result.
  • Bootstrap gives you finished components. You get a navbar, modal and grid out of the box, which is faster to start but harder to make look unique.

Neither is better in the abstract. Choose Bootstrap for a quick internal tool, and Tailwind when you want a bespoke design system without writing every rule by hand. The Bootstrap guide covers the other side.

Best practices

  • Learn CSS first; Tailwind is a shorthand, not a replacement.
  • Keep class lists readable by grouping related utilities and using the official formatter.
  • Extract a component when a pattern repeats, and use @apply only when you must.
  • Prefer theme tokens over arbitrary values so your design stays consistent.
  • Use the group and peer variants instead of adding state classes with JavaScript.
  • Let the build step purge unused styles — never hand-write a separate Tailwind file.

Common mistakes

  • Fighting the class list by reaching for @apply too early.
  • Copy-pasting the same long list instead of extracting a component.
  • Using arbitrary values everywhere and losing the shared scale.
  • Forgetting that responsive variants are mobile-first.
  • Assuming Tailwind replaces the need to understand the cascade and specificity.
  • Mixing Tailwind with a separate custom stylesheet and creating conflicting rules.

Where to go next

Tailwind is one answer to “how should I style this?”. Compare it with CSS Modules for scoped, hand-written CSS, and understand the PostCSS pipeline that makes it run. Whichever you choose, the CSS fundamentals underneath are what make you effective.

Styling a button

Tailwind keeps the styling next to the markup. The custom-CSS version forces you to invent a class name and jump files.

Tailwind
<button class="rounded-lg bg-blue-600 px-4 py-2
               text-sm font-medium text-white
               hover:bg-blue-500">
  Save
</button>
Custom CSS
<button class="btn-primary">Save</button>

<style>
  .btn-primary {
    border-radius: 0.5rem;
    background: #2563eb;
    padding: 0.5rem 1rem;
    color: #fff;
  }
</style>

Responsive and dark mode

Variants stack. This element is padded differently on small screens and in dark mode.

Prefer
<div class="p-4 md:p-8
            bg-white dark:bg-zinc-900">
</div>
Avoid
/* two separate media queries,
   duplicated properties */

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Tailwind CSS?

Our interactive tutorial walks you through Tailwind CSS step by step — with quizzes and real code you can run in the browser.