Why utilities
How class detection works
Understand why Tailwind scans complete class names in source files and why building class names dynamically can leave required CSS out of the build.
8 minute lesson
Tailwind scans your source as text and finds complete class names.
This works:
const colors = {
success: 'bg-green-600 text-white',
error: 'bg-red-600 text-white',
}
This does not work reliably:
const className = `bg-${color}-600`
Tailwind does not execute your JavaScript or understand interpolation. It sees text tokens. Since bg-green-600 and bg-red-600 never appear completely, their CSS may not be generated.
Map application states to complete, validated class strings:
const colorClasses = {
success: 'bg-green-600 text-white hover:bg-green-700',
error: 'bg-red-600 text-white hover:bg-red-700',
}
const className = colorClasses[state]
This is also a better component API. Callers choose a semantic state, while the component owns the actual design decisions.
Tailwind v4 automatically ignores common non-source inputs including node_modules, binary files, lockfiles, and files excluded by .gitignore. If a real component library is outside detection, register it explicitly in CSS:
@import "tailwindcss";
@source "../node_modules/@acme/ui";
In a monorepo, set an intentional base with source() or add explicit sources. Use @source inline() only when a generated class genuinely cannot appear in static source. A huge safelist hides design and build mistakes and increases output.
When a utility is missing, search the compiled CSS for its escaped selector. If it is absent, investigate detection. If it exists but does not apply, investigate the selector, variant condition, and cascade instead.
Your action is to reproduce a missing dynamic color class, replace it with a complete mapping, and confirm the rule appears in built CSS. Then identify one ignored path in your project and decide whether it should stay ignored or become an explicit source.
Lesson completed