Understanding tailwind.config
A design system, expressed as a config file.
What tailwind.config.js really is, the extend vs override distinction, and why a good config makes the whole project easier to maintain.
The config is the design system.
Tailwind's utility classes don't come from nowhere — they're generated from the config's theme object. Define a custom colour palette and bg-brand-500 exists. Add a custom font-size scale and text-display works. The config is therefore the single source of truth for the design tokens: colours, spacing, type sizes, breakpoints, shadows. Edit one config; every class across the project picks up the change.
extend vs replace.
The theme top-level keys (colors, spacing, fontSize) replace Tailwind's defaults entirely. theme.extend adds to them. For colours, most projects extend (keep Tailwind's grey scale, add brand colours); for spacing, almost always extend (the default scale is good). Override deliberately when the defaults conflict with the design system — usually only for breakpoints (matching a specific design's responsive grid).
A worked config.
module.exports = {
content: ["./src/**/*.{tsx,jsx,html}"],
theme: {
extend: {
colors: {
brand: { 500: "#6366f1", 600: "#4f46e5" },
ink: { DEFAULT: "#111", muted: "#666" }
},
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
serif: ["Source Serif Pro", "Georgia", "serif"]
},
spacing: { 18: "4.5rem", 88: "22rem" }
}
}
}; From this config, classes like bg-brand-500, text-ink-muted, font-serif, p-18 all exist. The design system is one file.
Token → class
theme.extend.colors.brand[500] = '#6366f1'
The token name becomes the utility class suffix.
bg-brand-500 → background-color: #6366f1
= One source of truth
The content array is critical.
Tailwind v3+ scans every file matched by content for class names, generates CSS only for the classes it finds, and ships a tiny stylesheet (a few hundred KB → 8-20 KB after purge). Set content too narrow and classes silently don't get generated; too wide and the build is slower than it needs to be. The default ./src/**/*.{tsx,jsx,html} covers most projects.
v4 changed the config format.
Tailwind v4 (2024) shifted from a JavaScript config to CSS-first configuration via @theme blocks inside the stylesheet itself. Same concept (tokens defined, classes generated), different syntax. The migration path accepts both during the transition; greenfield v4 projects use CSS-only; existing v3 projects keep the JS config until they migrate. The conceptual model is unchanged.
Don't sneak past the tokens.
The temptation when the design demands an off-system colour or spacing: use an arbitrary value: bg-[#fa3c00], p-[18px]. Sometimes right (genuinely one-off values), often wrong (the design system probably wants this colour or spacing as a real token). Each arbitrary value is a chance to ask: "should this be a token instead?". Long-term maintainability comes from saying yes most of the time.