Intro
Last modified on Wed 25 Jun 2025

Introduction to Tailwind CSS

Tailwind CSS is a utility-first styling framework. Instead of shipping pre-styled components or encouraging semantic class names, Tailwind exposes thousands of small, composable classes, each mapping one-to-one with a single CSS declaration:

You place these classes directly in your JSX. When the build runs Tailwind's compiler, now called Oxide (rewritten in Rust for v4), scans every template, generates only the CSS you referenced, minifies it, and removes everything else. The output is a single static stylesheet with zero runtime JavaScript.

A Three-Second Mental Model

  1. Everything is opt-in
    If you never write rounded, nothing gets rounded. No default design decisions are imposed.

  2. Markup is the source of truth
    Delete a component in React and its styles vanish automatically. There is no orphan stylesheet to clean up.

  3. Classes read like English
    flex flex-col gap-6 lg:flex-row items-center bg-surface p-8 tells you the layout, spacing, breakpoint variation, alignment, colour and padding in one glance.

  4. Design tokens are code
    Colours, spacing steps, radii and shadows live in tailwind.config.js (or in v4 inside an @theme block). Every token becomes a real CSS custom property, readable by JavaScript, Framer Motion or even a Figma plugin that scrapes the DOM.

How Tailwind Works Under the Hood

The JIT Compilation Cycle

  1. Template scan - Oxide reads your .tsx, .mdx, .html and any other file types you list. It tokenises class attributes, including the more exotic arbitrary value syntax like max-w-[42ch].
  2. Class lookup - Each token maps to a rule in Tailwind's internal AST. Tailwind also supports user-defined classes added through the theme.extend or @apply directives.
  3. CSS emission - Only referenced rules are emitted. Tailwind groups selectors that share the same declaration to keep file size minimal.
  4. Minification and purging - Duplicate declarations are collapsed, comments are stripped and the file is saved alongside the rest of your assets. Because unused CSS is never written in the first place, the purge step is effectively free.
  5. Incremental rebuilds - Oxide tracks a dependency graph. Editing a single component triggers micro-rebuilds that typically complete in under 50 ms even for large mono-repos.

Configuration in v4

@import "tailwindcss";

@theme {
  --radius-lg: 0.75rem;
  --brand-500: 34 197 94; /* R G B */
}

No separate JavaScript config file is required, and those custom properties are accessible at runtime:

const brand = getComputedStyle(document.documentElement).getPropertyValue(
  "--brand-500"
);

Comparing Tailwind to other styling solutions

Below comparison is focused on developer experience, performance and how well each option fits Next.js, React Server Components (RSC) and streaming.

Global CSS and SCSS

CSS/SCSS Modules

Styled JSX (Next.js default)

Emotion / Styled-Components

Stitches, Panda, Vanilla-Extract

UI Component Libraries (Chakra UI, Material UI)

Pros and Cons of Tailwind CSS

Strengths

Trade-offs

Why We Replaced Chakra UI with Tailwind

Concern Chakra UI Tailwind CSS
Bundle size 40-80 KB CSS, 25-60 KB JS < 30 KB CSS, negligible JS
Runtime cost Emotion creates class names at runtime None
Theming Tied to Chakra's token names and scales Plain CSS variables, any naming
Custom variants extendTheme JS code Combine utilities or use @apply
Server comps Experimental hydration path Works out of the box
Refactors Prop explosion (_hover={{ bg: ... }}) Pure HTML classes, quick edits
  1. Token mapping - export the current Chakra (or any other) theme to JSON (use npx @chakra-ui/cli tokens). Copy colours, spacing, radii and font scales into Tailwind's @theme block. Preserve token names one-for-one so existing design language stays intact.
  2. Parallel Tailwind setup - Install Tailwind CSS with Next.js
  3. Component wrappers - introduce thin legacy shims such as <LegacyButton> or <LegacyBox>. Each shim keeps the Chakra prop API but renders plain HTML with Tailwind utilities. This lets you migrate page-by-page without a disruptive big-bang refactor.
  4. Incremental replacement - prioritize the highest-traffic or most visually shared primitives first (Button, Input, Modal). For each component:
  1. Design-system parity check - run a UI/UX audit once every Chakra component has a Tailwind counterpart. Compare variant, state and breakpoint coverage; update tokens or utilities until every design spec passes.

  2. Dependency removal - remove <ChakraProvider> and CSSReset, delete Chakra from package.json, and run grep to locate any lingering _hover or variant props.

  3. Ship and measure - deploy to staging and measure bundle size and Core Web Vitals against the previous build. Teams consistently see

    • JS bundle shrink ≈ 15-25 %
    • First Contentful Paint improve by 200-300 ms on a 3G Fast profile
    • Developer velocity increase thanks to clearer, inline class semantics

Common Concerns and How We Answer Them

Question Answer
Isn't Tailwind hard to read? After a short ramp-up, seeing colour, spacing and typography inline is faster than jumping to a CSS file
Will utility classes bloat the HTML? Gzip/Brotli compress repeating prefixes well; file size impact is minimal
Can designers work with Tailwind? Tokens live in one config file and are exported to Figma via the Tailwind Tokens plugin
What about dark mode or theming? Tailwind's dark: variant and CSS variables enable instant theme switching without re-rendering components

Further Reading