Tailwind CSS cheat sheet

A scannable Tailwind CSS reference: 38 short snippets across 13 topics, each linking back to the lesson it came from.

At a glance

TopicWhat it covers
Utility-first basicsInstall the toolchain, read a class list as the CSS it produces, and understand why the numbers in spacing utilitieslesson
Responsive and state variantsMobile-first breakpoints, the state variants that replace handwritten interaction CSS, and how group, peer and customlesson
Configuration and themingCSS-first theming in v4, the v3 JavaScript config for comparison, and when a custom utility beats a pile of @applylesson
Core layout utilities: flex, grid and spacingPrefer gap to space-x-*. Gap applies between items without touching the outside edges, it composes with wrapping, andlesson
Typography, colour and dark modeSet type with size and line-height together, build a colour system you can theme, and switch dark mode from a class orlesson
Component patterns without a component libraryBuild buttons, cards, badges, alerts and tables that stay consistent, and know when a class list deserves to become alesson
Forms, states and accessible stylinginvalid: fires as soon as the field is invalid, which for a required empty input is immediately on load - so everylesson
Transitions, transforms and animationMotion that supports the interface rather than decorating it: transitions, transforms, keyframes in the theme, andlesson
Arbitrary values, brackets and the scanning modelTailwind looks for class names as plain text in your source files. It does not execute your code and does not followlesson
Integrating with frameworks and build toolsOne entry point, one import. A second stylesheet that also imports Tailwind duplicates the preflight and the themelesson
Tailwind with component libraries and variant helpersCombine class lists safely with cn(), declare variants with cva, and integrate headless component libraries withoutlesson
Migrating from v3 to v4Run the upgrade tool, fix the renamed utilities that fail silently, and verify the result against the old build ratherlesson
Production performance and the honest limitsPreflight is why a Tailwind page has no unexpected spacing - and also why a page of unstyled HTML rendered inside yourlesson

Quick snippets

Utility-first basics

Installing and wiring it up

# v4 as a PostCSS plugin (Vite, webpack, Next.js)
npm install tailwindcss @tailwindcss/postcss

# v4 standalone CLI: compile and watch
npm install -D tailwindcss @tailwindcss/cli
npx @tailwindcss/cli -i ./src/input.css -o ./dist/output.css --watch

Installing and wiring it up

/* src/input.css (v4) */
@import "tailwindcss";

/* scan directories the automatic detection misses */
@source "../templates";

Reading a class list

<button class="inline-flex items-center gap-2 rounded-lg bg-indigo-600 px-4 py-2
               text-sm font-medium text-white shadow-sm hover:bg-indigo-500
               focus-visible:outline-2 focus-visible:outline-offset-2">
  Save changes
  <svg class="size-4" aria-hidden="true">...</svg>
</button>

Full lesson: Utility-first basics →

Responsive and state variants

Mobile first, then min-width

<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
  ...
</div>

<!-- one column stack on phones, two from 768px, four from 1280px -->
<article class="col-span-1 md:col-span-2 xl:col-span-4">...</article>

State variants

<a class="text-slate-600 transition-colors hover:text-slate-900
          focus-visible:outline-2 focus-visible:outline-offset-2
          aria-current:font-semibold aria-current:text-slate-900">Docs</a>

<ul class="group">
  <li class="group-hover:bg-slate-50 group-focus-within:bg-slate-50">Item</li>
</ul>

<div class="rtl:mr-4 rtl:ml-0 motion-reduce:transition-none">...</div>

Composing variants

<!-- name a group when they nest, so the outer one is not triggered -->
<div class="group/menu">
  <button class="group-hover/menu:text-indigo-600">Menu</button>
</div>

<!-- target siblings with peer; order in the DOM matters -->
<input id="ok" type="checkbox" class="peer sr-only">
<label for="ok" class="peer-checked:bg-emerald-600 peer-checked:text-white">Accept</label>

<!-- stack variants: on wide screens and only while hovered -->
<article class="lg:hover:shadow-lg">...</article>

Full lesson: Responsive and state variants →

Configuration and theming

Theming in v4

@import "tailwindcss";

@theme {
  --color-brand-500: oklch(0.62 0.19 262);
  --color-brand-600: oklch(0.55 0.19 262);
  --font-display: "Satoshi", ui-sans-serif, system-ui;
  --spacing: 0.25rem;          /* every numeric spacing step derives from this */
  --breakpoint-3xl: 120rem;
  --radius-card: 0.875rem;
}

The v3 configuration file

// tailwind.config.js - v3, and loadable in v4 with @config "./tailwind.config.js"
module.exports = {
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: { brand: { 500: '#4f46e5', 600: '#4338ca' } },
      fontFamily: { display: ['Satoshi', 'sans-serif'] },
      screens: { '3xl': '120rem' }
    }
  },
  plugins: [require('@tailwindcss/forms')]
};

Custom utilities and component classes

@utility content-auto {
  content-visibility: auto;
}

/* a semantic class for markup you do not control */
@layer components {
  .btn-primary {
    @apply inline-flex items-center rounded-lg bg-brand-500 px-4 py-2 text-white;
  }
}

Full lesson: Configuration and theming →

Core layout utilities: flex, grid and spacing

Flexbox

<div class="flex items-center justify-between gap-4 p-4">
  <div class="flex min-w-0 items-center gap-3">
    <img class="size-10 shrink-0 rounded-full object-cover" src="avatar.jpg" alt="">
    <div class="min-w-0">
      <p class="truncate font-medium">Ada Lovelace</p>
      <p class="truncate text-sm text-slate-500">[email protected]</p>
    </div>
  </div>
  <button class="shrink-0 rounded-md bg-slate-900 px-3 py-1.5 text-white">Edit</button>
</div>

Grid

<!-- named grid areas, expressed as an arbitrary property -->
<div class="grid grid-cols-3 grid-rows-[auto_1fr_auto]">
  <header class="col-span-3">Header</header>
  <main>Content</main>
  <aside>Sidebar</aside>
  <footer class="col-span-3">Footer</footer>
</div>

Spacing, sizing and positioning

/* the spacing scale is one variable, so changing it rescales everything */
@theme {
  --spacing: 0.25rem;      /* gap-4 is 1rem; set a different base and the whole UI follows */
}

Full lesson: Core layout utilities: flex, grid and spacing →

Typography, colour and dark mode

A colour system

@import "tailwindcss";

@theme {
  --color-surface: oklch(99% 0.002 250);
  --color-on-surface: oklch(21% 0.02 260);
  --color-border: oklch(92% 0.01 260);

  --color-brand-500: oklch(62% 0.19 250);
  --color-brand-600: oklch(55% 0.19 250);
}

Dark mode

@import "tailwindcss";

/* default: follow the operating system, no configuration needed
   dark:bg-slate-900  ->  @media (prefers-color-scheme: dark) */

/* class strategy: switch with a class on the html element */
@custom-variant dark (&:where(.dark, .dark *));

Dark mode

<html class="dark">
  <body class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100">
    <div class="rounded-lg border border-slate-200 bg-white p-6
                dark:border-slate-800 dark:bg-slate-900">
      <h2 class="text-slate-900 dark:text-slate-50">Title</h2>
      <p class="text-slate-600 dark:text-slate-400">Body copy</p>
    </div>
  </body>
</html>

Full lesson: Typography, colour and dark mode →

Component patterns without a component library

Cards, badges and alerts

<!-- alerts: a role, an icon and a colour that matches the meaning -->
<div role="status" class="flex gap-3 rounded-lg bg-emerald-50 p-4 ring-1 ring-emerald-600/20">
  <svg class="size-5 shrink-0 text-emerald-600" aria-hidden="true">...</svg>
  <div>
    <p class="font-medium text-emerald-900">Invoice sent</p>
    <p class="text-sm text-emerald-800">The customer will receive it within a minute.</p>
  </div>
</div>

<div role="alert" class="flex gap-3 rounded-lg bg-red-50 p-4 ring-1 ring-red-600/20">
  <p class="font-medium text-red-900">Payment failed</p>
</div>

When to extract a component

<!-- before: the caller decides everything -->
<div class="flex items-center gap-2 rounded-md px-3.5 py-2 bg-sky-600 text-white">Save</div>
<div class="flex items-center gap-2 rounded-md px-3.5 py-2 bg-red-600 text-white">Delete</div>
<div class="flex items-center gap-2 rounded-md px-3 py-2 bg-slate-100 text-slate-500">Cancel</div>

<!-- after: one component, three variants -->
<Button variant="primary">Save</Button>
<Button variant="danger">Delete</Button>
<Button variant="ghost" size="sm">Cancel</Button>

When to extract a component

A note on class soup:

Long class lists are a real cost, but the alternative - a stylesheet of
bespoke classes - moves the same complexity somewhere harder to see.
The productive middle is: utilities for one-off layout, a component for
anything used three times with more than two variants, and tokens for
anything that must stay consistent across both.

Full lesson: Component patterns without a component library →

Forms, states and accessible styling

Inputs across browsers

<label class="block">
  <span class="mb-1.5 block text-sm font-medium text-slate-700">Email</span>
  <input type="email" name="email" required autocomplete="email"
         class="block w-full rounded-md border border-slate-300 bg-white px-3 py-2
                text-sm text-slate-900 placeholder:text-slate-400
                focus:border-sky-500 focus:outline-2 focus:outline-offset-0 focus:outline-sky-500
                disabled:cursor-not-allowed disabled:bg-slate-50 disabled:text-slate-400
                read-only:bg-slate-50">
</label>

Inputs across browsers

/* the forms plugin normalises the native appearance across browsers */
@import "tailwindcss";
@plugin "@tailwindcss/forms";

Styling that carries the semantics

<button class="min-h-11 min-w-11 rounded-md border transition
               motion-reduce:transition-none"
        aria-label="Close dialog">
  <svg class="mx-auto size-5" aria-hidden="true">...</svg>
</button>

Full lesson: Forms, states and accessible styling →

Transitions, transforms and animation

Transitions

<button class="rounded-md bg-sky-600 px-4 py-2 text-white
               transition-colors duration-150 ease-out
               hover:bg-sky-500 active:bg-sky-700">
  Save
</button>

<div class="opacity-0 transition-opacity duration-200 group-hover:opacity-100">Revealed</div>

<div class="scale-95 opacity-0 transition duration-200 ease-out
            data-[state=open]:scale-100 data-[state=open]:opacity-100">
  Popover
</div>

Transforms

<div class="translate-x-0 transition-transform hover:translate-x-2">Nudge right</div>
<div class="rotate-3 transition-transform hover:rotate-0">Straighten on hover</div>
<div class="scale-100 transition-transform hover:scale-105 active:scale-95">Pressable</div>
<div class="origin-top-left rotate-45">Rotate around a corner</div>

<!-- combining several transforms: they compose through the same variables -->
<div class="translate-y-1 rotate-2 scale-105">Composed</div>

<!-- a centred overlay without knowing its size -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">Centered</div>

Transforms

<!-- a slide-in drawer -->
<aside class="fixed inset-y-0 right-0 w-80 translate-x-full
              transition-transform duration-300 ease-out
              data-[open=true]:translate-x-0">
  ...
</aside>

Full lesson: Transitions, transforms and animation →

Arbitrary values, brackets and the scanning model

How the scanner finds classes

@import "tailwindcss";

/* scan an extra source that is outside the default detection */
@source "../node_modules/@acme/ui";

/* exclude something that produces false positives */
@source not "../fixtures";

/* declare a set of class names that only exist at runtime */
@source inline("grid-cols-{1,2,3,4}");

Making dynamic classes safe

<!-- data attributes carry the variant, so the classes stay static -->
<div data-state="open" class="hidden data-[state=open]:block">...</div>
<div data-tone="danger" class="text-slate-600 data-[tone=danger]:text-red-600">...</div>

<!-- group and peer variants for parent-driven state -->
<div class="group" data-loading="true">
  <span class="opacity-100 group-data-[loading=true]:opacity-50">...</span>
</div>

Making dynamic classes safe

// a lookup map keeps every class name in the source
const GRID = {
  1: "grid-cols-1",
  2: "grid-cols-2",
  3: "grid-cols-3",
  4: "grid-cols-4",
};

function gridCols(n) {
  return GRID[n] ?? GRID[1];
}

Full lesson: Arbitrary values, brackets and the scanning model →

Integrating with frameworks and build tools

Vite and PostCSS

# Vite: the dedicated plugin is the fastest path
npm install tailwindcss @tailwindcss/vite

Vite and PostCSS

// vite.config.ts
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [tailwindcss()],
});

Vite and PostCSS

/* src/app.css - the single entry point */
@import "tailwindcss";

/* components and utilities you own */
@layer components {
  .card { @apply rounded-xl border border-slate-200 bg-white p-5; }
}

Full lesson: Integrating with frameworks and build tools →

Tailwind with component libraries and variant helpers

Merging class lists

npm install clsx tailwind-merge

Merging class lists

// lib/cn.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Merging class lists

// the problem: both classes win or lose by stylesheet order, not by intent
<div class="p-4 p-8">...</div>          // unpredictable

// with cn(), the last one wins as a real override
cn("p-4", "p-8");                        // "p-8"
cn("p-4", condition && "p-8");            // "p-4" when false
cn("text-slate-600", props.className);    // the caller's class wins

Full lesson: Tailwind with component libraries and variant helpers →

Migrating from v3 to v4

The upgrade tool

# on a clean working tree, on a branch
git switch -c tailwind-v4
git status --short          # must be empty

npx @tailwindcss/upgrade@latest

# then read the diff before running anything
git diff --stat
git diff -- src/app.css tailwind.config.js

The upgrade tool

# before the migration
npx @tailwindcss/cli -i src/app.css -o /tmp/before.css --minify

# after
npx @tailwindcss/cli -i src/app.css -o /tmp/after.css --minify
wc -c /tmp/before.css /tmp/after.css

Configuration and the container plugin

/* a legacy JavaScript config still works, explicitly */
@config "../tailwind.config.js";
@import "tailwindcss";

Full lesson: Migrating from v3 to v4 →

Production performance and the honest limits

Output size

# build, then look at what you actually shipped
npx @tailwindcss/cli -i src/app.css -o dist/app.css --minify
wc -c dist/app.css
gzip -9 -c dist/app.css | wc -c

# find the outliers: a class you did not expect is usually a source you forgot
grep -o '\.[a-z-]*\\[[^]]*\]' dist/app.css | sort | uniq -c | sort -rn | head

When not to use Tailwind

A practical split that works well:

Tailwind      layout, spacing, typography, state variants, one-off adjustments
CSS Modules   complex selectors, keyframe-heavy components, third-party markup
Custom props  anything that must change at runtime or per tenant

Full lesson: Production performance and the honest limits →

FAQ

Is this Tailwind CSS cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 13 lessons of the Tailwind CSS course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Tailwind CSS course — it carries the worked explanations, the edge cases and the exercises behind every line here.

HTML CSS JavaScript TypeScript HTML DOM AJAX

Last refreshed 2026-09-27.