This episode dissects tailwind.config.js fully: granular tokens like colors, spacing, borderRadius, fonts, and zIndex, using presets to share config across projects, and safelist for handling dynamic classes that aren't detected by the JIT engine.

tailwind.config.js is the control center of your entire Tailwind project. In episode 8 we dissect it in depth: granular tokens for colors, spacing, radius, fonts, and z-index; presets for sharing configuration across projects; and safelist for dynamic class scenarios that escape JIT.
The habit of writing a clean config pays off in large projects. The more tokens are defined centrally, the fewer magic values get scattered in markup, and the easier it is for design and engineering teams to agree on one visual language.
Colors can be defined as named palettes with value scales:
theme: {
extend: {
colors: {
brand: {
50: "#eff6ff",
100: "#dbeafe",
500: "#3b82f6",
600: "#2563eb",
900: "#1e3a8a",
},
},
},
},Each key becomes a utility: bg-brand-500, text-brand-600, border-brand-900. You can also combine the brand object with built-in colors — extend doesn't remove the defaults.
Other tokens follow the same pattern:
theme: {
extend: {
spacing: {
128: "32rem",
144: "36rem",
},
borderRadius: {
"4xl": "2rem",
},
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
},
zIndex: {
max: "9999",
},
},
},New spacing is immediately available as p-128, m-144, w-128. The 4xl borderRadius is used as rounded-4xl. The sans font replaces the built-in family, and z-max provides an extreme z-index value. All utilities flow automatically from these tokens.
For multiple projects that share a design language, extract the tokens into a preset:
// tailwind-preset.js
module.exports = {
theme: {
extend: {
colors: {
brand: {
500: "#3b82f6",
},
},
},
},
};Then use it in every project:
const preset = require("@org/tailwind-preset");
module.exports = {
presets: [preset],
content: ["./src/**/*.{html,js,ts,jsx}"],
theme: { extend: {} },
plugins: [],
};presets: [preset] makes theme, plugin, and variant configuration shareable across projects — the foundation of the design system we'll build in episode 10. Presets can be overridden per project via extend.
JIT only generates classes found as complete strings in your content. Classes assembled dynamically at runtime won't be picked up. The solution is safelist:
module.exports = {
content: ["./src/**/*.{html,js,ts,jsx}"],
safelist: [
"bg-red-500",
"text-center",
{
pattern: /bg-(red|green|blue)-(500|600)/,
},
],
};bg-red-500 is always generated. The regex pattern pattern: /bg-(red|green|blue)-(500|600)/ covers every matching combination — suitable for limited, known class sets.
Safelist is not a solution for arbitrarily building classes like bg- glued to a color name from a database. For that, use explicit mapping: store full class strings in a map instead of assembling class names piece by piece. This strategy is also a security bulwark — covered fully in episode 11.
Warning
Safelist increases CSS size because classes are generated regardless of whether they're used. Use it as little as possible, and prefer explicit mappings stored as complete strings in your content.
Episode 8 completed your config understanding: granular tokens for colors, spacing, radius, fonts, and z-index; presets for sharing configuration; and safelist for handling limited, known dynamic classes.
Key takeaways:
theme.extend immediately become usable utilities.brand.500, brand.900.safelist forces classes to be generated even when JIT doesn't detect them.Next, in episode 9, we'll cover plugins & extending Tailwind — writing custom plugins for utilities, components, and variants, leveraging official plugins like forms and typography, and getting to know the public ecosystem like Tailwind UI, Headless UI, and daisyUI.