Skip to main content
ASoc
Tutorial

What Is Tailwind Used For? 908 Routes, One 15 KB Stylesheet

What Tailwind actually does for a production site, what it refuses to do, and the measured cost: 41 tokens, 92 components, 15,162 gzipped bytes of CSS.

The ASoc Team9 min read

Tailwind CSS is used to style web interfaces by writing single-purpose utility classes in your markup — flex, p-6, text-gray-900 — instead of hand-authoring CSS in separate files. Its compiler then emits only the classes your source actually used, so the stylesheet grows with your design rather than with your page count.

That last clause is the part the definitions usually skip, and it is the one with a number attached. This storefront is built with Tailwind v4: 908 prerendered routes ship from one 15,162-byte gzipped stylesheet (85,540 bytes raw), measured from the production build while writing this post. Below is what Tailwind is actually doing for those pages, what it is not doing, and where it costs something.

What Tailwind is used for, concretely

JobHow Tailwind does itWhat it replaces
Layout and spacingUtility classes in the markup (flex, grid, gap-6, p-4)Hand-written layout CSS per component
Design tokensCSS custom properties declared once, in @themeA Sass variables file, or hardcoded hexes
Responsive behaviourBreakpoint prefixes (md:p-6, lg:grid-cols-3)Media-query blocks
State stylingVariant prefixes (hover:, focus-visible:, aria-disabled:)Separate selectors, often with duplicated base rules
Dark modeA dark: variant on each element that differsA second theme stylesheet
Dead-CSS removalIt is never generated — the compiler scans your sourcePurgeCSS, or periodic manual audits

And what it is not used for, which is worth stating plainly because half the "what is Tailwind" confusion is category error:

  • It is not a component library. There is no Modal, no Dropdown, no date picker. Tailwind styles; your framework behaves. (That is also why "Tailwind vs React" is not a real comparison — see Next.js vs Tailwind CSS.)
  • It is not a design system. It gives you a token mechanism. The decisions are still yours.
  • It is not a build tool. It plugs into one.

The utility-first trade, in one component

Here is a real molecule from this repo — the whole of src/components/molecules/FeatureCard.tsx minus its imports:

<div className="rounded-3xl border border-stroke-secondary bg-gray-50 p-1 duration-200 hover:border-primary-200 hover:bg-primary-25 md:p-2">
  <div className="h-full rounded-2xl border border-[#F2F4F7] bg-white p-4 md:p-6">
    <div className="mb-7.5 text-primary">{icon}</div>
    <h3 className="mb-4 text-xl font-semibold text-title-color md:text-2xl lg:text-xl xl:text-2xl">
      {title}
    </h3>
    <p className="text-base !leading-normal text-text-color-secondary">
      {description}
    </p>
  </div>
</div>

Everything that decides how this card looks is in the file you are reading. Nothing to open in a stylesheet, no class name to invent, no question about whether some other component also uses .feature-card. That is the actual benefit, and it is a maintenance benefit rather than an aesthetic one.

The cost is equally visible: that is a lot of text in the markup. Two things make it tolerable in practice, both load-bearing here.

One: the component is the abstraction. This card is written once and the organism maps over a data array to render all of them. Utility classes are verbose per element and the element count is what you control.

Two: a formatter sorts the classes. This repo runs prettier-plugin-tailwindcss, so class order is canonical and never a diff. Without it, two people editing the same className produce conflicting orders of identical classes, and review time goes into noise.

Note text-title-color and bg-primary-25 in that markup — those are not Tailwind's built-in palette. They are this project's tokens, which is the next section.

Tokens: the part that makes it a design system

Tailwind v4 is configured in CSS, not JavaScript. There is no tailwind.config.js here. src/app/globals.css is 133 lines, and its @theme block declares 41 tokens, 38 of them colors:

@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));

@theme {
  --font-sans: var(--font-outfit), ui-sans-serif, system-ui, sans-serif;

  --color-primary: #465fff;
  --color-primary-25: #f2f7ff;
  /* …through primary-950 */

  --color-gray-900: #101828;

  --color-text-color: #344054;
  --color-text-color-secondary: #667085;
}

Declaring --color-primary-25 is what generates bg-primary-25, text-primary-25, border-primary-25 and the rest — one declaration, every utility that could use it. Rename the hex and all 908 routes change together. Those names are also real CSS custom properties at runtime, so a theme can be swapped without a rebuild.

Two semantic tokens in that list (text-color, title-color) exist so that "the color body copy is" is a decision stated once rather than a gray-700 repeated across 92 components. That distinction — scale tokens versus semantic tokens — is most of what separates a token system from a renamed palette.

A defect this configuration actually produced

Worth knowing before you spend an hour on it, because the failure mode is silence rather than an error.

Tailwind v4 with Turbopack does not hot-reload @theme changes. Add --color-brand: #ff0066 to the block, save, use bg-brand in a component, and nothing happens — the class is simply not generated, and the element renders unstyled as though you had typed the name wrong. There is no console warning, because from the browser's point of view no such utility exists.

The fix is to restart the dev server. It is now a warning in this repo's CLAUDE.md, directly above the token list, because every new contributor hits it once:

⚠️ Tailwind v4 + Turbopack does not hot-reload @theme changes — restart npm run dev after editing color/token vars in globals.css (otherwise new utilities like bg-primary silently fail to generate).

The general shape of this is familiar from any compiled-config system: the theme is read once at startup, so a change to the theme is a change to the compiler's input, not to its output. Treat "my new utility class does nothing" as a restart before treating it as a typo.

Where the generated CSS actually lands

The claim "Tailwind only ships what you used" is checkable, so here it is checked on this build:

MeasurementValue
Prerendered routes908
Stylesheets emitted1
Raw size85,540 bytes
Gzipped15,162 bytes
@theme tokens declared41 (38 colors)
Components drawing from them92

One file for the whole site, because the utilities repeat across pages and a repeated class is one rule. This is the mechanism that makes utility-first cheap at scale: the stylesheet is a function of how many distinct utilities you used, not how many pages or components used them. A 909th route that reuses existing utilities adds nothing to that 15 KB.

Tailwind's contribution to the rest of the page budget is mostly negative work: no runtime style injection, and no layout shift from a late stylesheet. This site's last full Lighthouse pass recorded accessibility and SEO at 100 on every page on both form factors, with CLS at 0 — Tailwind does not earn those on its own, but a styling layer that injected CSS at runtime would be competing with them.

Mistakes and how they show up

SymptomCauseFix
A new token's utility does nothing in dev@theme is read at startup, not hot-reloadedRestart the dev server
A class works locally, missing in productionClass name assembled from a variable, so the scanner never saw itWrite whole class names; map through a Record
Class order changes in every diffNo formatter pluginprettier-plugin-tailwindcss
Markup is unreadableStyling elements that should be one componentExtract the component, not a CSS class
Dark mode works on some elements onlydark: is per-element; new markup needs its ownAdd the variant when you add the element
Looking for tailwind.config.js in v4Config moved into CSS@theme in your stylesheet
@apply everywhere, back to a stylesheetUsing it to recreate semantic classesUse it sparingly; prefer components

Frequently asked questions

What is Tailwind CSS used for, in one sentence? Styling markup with utility classes and a token system, so that a component's appearance lives in the component and the shipped stylesheet contains only what the project actually used.

Do I still need to know CSS? More than with a component library, not less. Every utility maps to one CSS declaration, so items-center is only useful if you know what align-items: center does. Tailwind changes where you write CSS, not whether you understand it.

Is Tailwind good for large projects? The 15,162-byte figure above is from 908 routes and 92 components, which is the honest version of the answer: the CSS does not grow with the project, and the discipline it needs is component extraction rather than stylesheet organisation. The failure mode at scale is duplicated markup, not duplicated CSS.

What is the difference between Tailwind and Bootstrap? Bootstrap gives you designed components (a navbar, a card) that look like Bootstrap until you override them. Tailwind gives you the primitives and no appearance of its own. Bootstrap is faster to a recognisable page; Tailwind is faster to a specific one. The longer version is in Tailwind vs Bootstrap for dashboards.

Can I use Tailwind without a build step? There is a CDN script for prototyping, but it ships the compiler to the browser and generates CSS at runtime. For anything real, the build step is the point — it is what produces the 15 KB instead of everything Tailwind could emit.

Pages with the token layer already wired

If the goal is a finished page rather than a theme architecture, ASoc Zenith, ASoc Aegis and ASoc Ally are landing sets with the @theme block, the semantic tokens and the dark-mode variants already written and measured.

Browse the sets: Next.js landing page templates and Tailwind landing page templates.

Keep reading

Tutorial8 min read

What Is a Web Page Template? Two Products Share the Word

A hosted design you edit in a builder, and a source repo you own, are both called templates. What is actually inside one, from a catalog of 111.

Read more
Tutorial8 min read

Where to Find Your Supabase URL (and What Breaks If It's Wrong)

The Project URL, the dashboard URL and the connection string look alike. Four files read it here, one accepts the wrong value silently, and an unset one errors every page.

Read more
Tutorial8 min read

Where to Put Components in Next.js: 92 Files, None of Them in app/

The App Router routes special files, not folders — so the placement question is about one import path and one place to look, not about avoiding accidental routes.

Read more