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
| Topic | What it covers | |
|---|---|---|
| Utility-first basics | Install the toolchain, read a class list as the CSS it produces, and understand why the numbers in spacing utilities | lesson |
| Responsive and state variants | Mobile-first breakpoints, the state variants that replace handwritten interaction CSS, and how group, peer and custom | lesson |
| Configuration and theming | CSS-first theming in v4, the v3 JavaScript config for comparison, and when a custom utility beats a pile of @apply | lesson |
| Core layout utilities: flex, grid and spacing | Prefer gap to space-x-*. Gap applies between items without touching the outside edges, it composes with wrapping, and | lesson |
| Typography, colour and dark mode | Set type with size and line-height together, build a colour system you can theme, and switch dark mode from a class or | lesson |
| Component patterns without a component library | Build buttons, cards, badges, alerts and tables that stay consistent, and know when a class list deserves to become a | lesson |
| Forms, states and accessible styling | invalid: fires as soon as the field is invalid, which for a required empty input is immediately on load - so every | lesson |
| Transitions, transforms and animation | Motion that supports the interface rather than decorating it: transitions, transforms, keyframes in the theme, and | lesson |
| Arbitrary values, brackets and the scanning model | Tailwind looks for class names as plain text in your source files. It does not execute your code and does not follow | lesson |
| Integrating with frameworks and build tools | One entry point, one import. A second stylesheet that also imports Tailwind duplicates the preflight and the theme | lesson |
| Tailwind with component libraries and variant helpers | Combine class lists safely with cn(), declare variants with cva, and integrate headless component libraries without | lesson |
| Migrating from v3 to v4 | Run the upgrade tool, fix the renamed utilities that fail silently, and verify the result against the old build rather | lesson |
| Production performance and the honest limits | Preflight is why a Tailwind page has no unexpected spacing - and also why a page of unstyled HTML rendered inside your | lesson |
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 winsFull 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 tenantFull lesson: Production performance and the honest limits →
FAQ
Is this Tailwind CSS cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
HTML CSS JavaScript TypeScript HTML DOM AJAX
Last refreshed 2026-09-27.