Tailwind CSS 71 🎨 AST Code Scrapes and JIT On-Demand Utility Class Generation
The just-in-time engine is the heart of modern Tailwind. It does not generate the entire utility set and rely on the browser to sort out which classes are used. It reads the source files, extracts the class candidates, and generates the CSS only for the candidates it found. The v4 engine extends the approach with the plain-text scanning and the Lightning CSS parsing, and the result is the build that produces the exact CSS the application needs. This chapter covers the scanning, the extraction, the generation, and the boundaries that make the JIT predictable.
Key point: The JIT engine does not parse the JavaScript or the JSX. It reads the files as plain text, extracts the candidate class strings with the regex, and generates the CSS for the candidates. A class name that is not in the source as the literal string is invisible to the engine, and the dynamic construction text-${color}-600 produces no CSS.
Why the JIT engine exists
The unused-utility problem. The classic Tailwind generated the entire utility set, and the production build relied on the purge to remove the unused. The JIT generates the used utilities from the start, and the output is the minimal.
The arbitrary-value problem. The classic could not generate the text-[#1da1f2] because the value was not in the predefined set. The JIT parses the arbitrary value and generates the CSS on the fly.
The build-time problem. The classic build produced the large CSS file, and the browser parsed it. The JIT produces the small CSS, and the build is the fast. The v4 engine reports the roughly 10x faster full builds and the 100x faster incremental.
The content-detection problem. The v4 detects the source files automatically. The .gitignore is respected, the binary files are ignored, and the Vite plugin uses the module graph. The @source directive adds the explicit paths.
The dynamic-class problem. The string concatenation produces the class the engine cannot see. The developer must use the static names or the CSS variables. The bg-[--accent] form is the pattern the engine supports.
The safelist problem. The class that is constructed at the runtime must be preserved. The @source inline(...) and the safelist are the mechanisms, and the pattern is the explicit.
The performance problem. The scanning is the plain-text regex, and the parsing is the Lightning CSS. The two are the fast, and the engine scales to the large projects.
a. The scanning and the extraction
The engine crawls the project and extracts the class candidates. The extraction is the plain-text regex, and the candidates are the strings.
The scanning
The v4 engine detects the source files automatically. The detection respects the .gitignore, ignores the binary extensions, and uses the Vite module graph when the Vite plugin is used.
@import "tailwindcss";
@source "../../packages/ui/src/**/*.{ts,tsx}";
@source not "./src/vendor/**/*.js";
The @source adds the explicit paths, and the @source not excludes.
The extraction
The engine reads the files as plain text. The regex extracts the candidate class strings, and the strings become the input to the generation.
// The source
<div class="flex items-center bg-blue-600 p-4">...</div>
// The candidates
// flex, items-center, bg-blue-600, p-4
The extraction does not parse the JavaScript. The class="...", the className="...", the tw="...", and the other patterns are the recognized.
The candidate forms
| The candidate | The example |
|---|---|
| The plain utility | bg-blue-600 |
| The variant | hover:bg-blue-700 |
| The arbitrary value | bg-[#1da1f2] |
| The arbitrary property | [mask-type:luminance] |
| The negative | -mt-4 |
| The important | bg-red-500! |
| The stacked variant | dark:md:hover:bg-blue-700 |
The engine recognizes the forms and generates the CSS for each.
The dynamic class problem
// ❌ The engine cannot see the constructed class.
const className = `text-${color}-600`;
// ✅ The static class.
const className = color === 'red' ? 'text-red-600' : 'text-blue-600';
// ✅ The CSS variable.
<div class="text-[--accent]" style="--accent: #1da1f2">
The string template produces the class the engine cannot see, and the CSS is not generated. The static form or the CSS variable is the fix.
The extraction table
| The pattern | The extracted |
|---|---|
class="..." | The classes |
className="..." | The classes |
tw="..." | The classes |
| The template literal | The static parts |
| The string concat | The parts |
The content detection
| The source | The mechanism |
|---|---|
| The automatic | The .gitignore, the binary ignore |
| The Vite | The module graph |
The @source | The explicit path |
The @source not | The exclusion |
b. The JIT generation
The generation takes the candidates and produces the CSS. The generation is the on-demand, and the output is the minimal.
The utility generation
The engine resolves the utility from the candidate. The bg-blue-600 is the background-color with the value from the theme. The theme is the @theme block, and the value is the token.
@theme {
--color-blue-600: oklch(0.55 0.2 260);
}
The bg-blue-600 produces the .bg-blue-600 { background-color: var(--color-blue-600) }.
The variant generation
The variant wraps the utility in the selector. The hover:bg-blue-600 produces the .hover\:bg-blue-600:hover { ... }.
// The source
<button class="hover:bg-blue-600">Click</button>
// The generated CSS
.hover\:bg-blue-600:hover {
background-color: var(--color-blue-600);
}
The arbitrary value generation
The arbitrary value is the inline. The bg-[#1da1f2] produces the .bg-\[\#1da1f2\] { background-color: #1da1f2 }.
<div class="bg-[#1da1f2]">
// The generated
.bg-\[\#1da1f2\] {
background-color: #1da1f2;
}
The engine parses the value and generates the declaration.
The stacked variant generation
The stacked variants produce the nested selectors. The dark:md:hover:bg-blue-600 produces the @media (min-width: 768px) with the .dark ancestor and the :hover.
@media (min-width: 768px) {
.dark .dark\:md\:hover\:bg-blue-600:hover {
background-color: var(--color-blue-600);
}
}
The engine composes the variants in the order.
The important generation
The ! suffix produces the !important. The bg-red-500! produces the .bg-red-500\! { background-color: var(--color-red-500) !important }.
The negative generation
The - prefix produces the negative. The -mt-4 produces the .-\mt-4 { margin-top: calc(var(--spacing-4) * -1) }.
The generation table
| The candidate | The generated |
|---|---|
bg-blue-600 | The declaration |
hover:bg-blue-600 | The :hover selector |
bg-[#1da1f2] | The inline value |
dark:md:hover:... | The nested |
bg-red-500! | The !important |
-mt-4 | The negative |
c. The cache and the incremental
The JIT engine caches the generated CSS and re-uses it. The incremental build re-generates only the changed.
The candidate cache
The engine keeps the map from the candidate to the generated CSS. The repeated candidate produces the cached CSS, and the generation is the fast.
flex → .flex { display: flex; }
items-center → .items-center { align-items: center; }
bg-blue-600 → .bg-blue-600 { background-color: ...; }
The cache is the in-memory, and the lookup is the hash map.
The incremental build
The v4 engine re-scans only the changed files. The new candidates are generated, and the removed candidates are dropped. The incremental build is the roughly 100x faster.
The file changed: src/App.jsx
The new candidates: the ones in the file
The generation: the new CSS for the new candidates
The build is the incremental, and the browser receives the style-only update.
The CSS-first configuration
The @theme block is the source of the tokens. The engine reads the block and generates the utilities from the tokens.
@import "tailwindcss";
@theme {
--color-primary: oklch(0.62 0.19 260);
--font-display: "Inter", sans-serif;
--radius-md: 0.375rem;
}
The --color-primary generates the bg-primary, the text-primary, and the border-primary.
The @utility and the @custom-variant
The custom utilities and the custom variants are defined in the CSS.
@utility tab-4 {
tab-size: 4;
}
@custom-variant dark (&:where(.dark, .dark *));
The @utility registers the custom utility, and the @custom-variant registers the custom variant.
The cache table
| Element | Purpose |
|---|---|
| The candidate cache | The map from the candidate |
| The incremental | The re-scan the changed |
The @theme | The tokens |
The @utility | The custom utility |
The @custom-variant | The custom variant |
The build performance
| The build | The time |
|---|---|
| The v3 full | The 960ms |
| The v4 full | The 105ms |
| The v4 incremental | The ~100x faster |
The performance is the headline of the v4.
Complete Example Session
// ============================================
// PART 1: THE SOURCE FILE
// ============================================
// src/App.jsx
export default function App() {
return (
<div className="flex items-center gap-4 bg-blue-600 p-4 text-white">
<h1 className="text-2xl font-bold">Hello</h1>
<button className="rounded-md bg-white px-4 py-2 text-blue-600 hover:bg-blue-50">
Click
</button>
</div>
);
}
// ============================================
// PART 2: THE EXTRACTED CANDIDATES
// ============================================
// flex, items-center, gap-4, bg-blue-600, p-4, text-white
// text-2xl, font-bold
// rounded-md, bg-white, px-4, py-2, text-blue-600, hover:bg-blue-50
// ============================================
// PART 3: THE GENERATED CSS
// ============================================
.flex { display: flex; }
.items-center { align-items: center; }
.gap-4 { gap: var(--spacing-4); }
.bg-blue-600 { background-color: var(--color-blue-600); }
.p-4 { padding: var(--spacing-4); }
.text-white { color: var(--color-white); }
.text-2xl { font-size: var(--text-2xl); }
.font-bold { font-weight: 700; }
.rounded-md { border-radius: var(--radius-md); }
.bg-white { background-color: var(--color-white); }
.px-4 { padding-inline: var(--spacing-4); }
.py-2 { padding-block: var(--spacing-2); }
.text-blue-600 { color: var(--color-blue-600); }
.hover\:bg-blue-50:hover { background-color: var(--color-blue-50); }
// ============================================
// PART 4: THE THEME TOKENS
// ============================================
@import "tailwindcss";
@theme {
--color-blue-600: oklch(0.55 0.2 260);
--color-blue-50: oklch(0.97 0.02 260);
--color-white: oklch(1 0 0);
--spacing-4: 1rem;
--spacing-2: 0.5rem;
--text-2xl: 1.5rem;
--radius-md: 0.375rem;
}
// ============================================
// PART 5: THE ARBITRARY VALUE
// ============================================
// <div className="bg-[#1da1f2]">
.bg-\[\#1da1f2\] {
background-color: #1da1f2;
}
// ============================================
// PART 6: THE STACKED VARIANT
// ============================================
// <div className="dark:md:hover:bg-blue-700">
@media (min-width: 768px) {
.dark .dark\:md\:hover\:bg-blue-700:hover {
background-color: var(--color-blue-700);
}
}
// ============================================
// PART 7: THE DYNAMIC CLASS PROBLEM
// ============================================
// ❌ const className = `text-${color}-600`;
// ✅ const className = color === 'red' ? 'text-red-600' : 'text-blue-600';
// ✅ <div className="text-[--accent]" style={{ '--accent': '#1da1f2' }}>
// ============================================
// PART 8: THE SOURCE DIRECTIVE
// ============================================
@import "tailwindcss";
@source "../../packages/ui/src/**/*.{ts,tsx}";
@source not "./src/vendor/**/*.js";
// ============================================
// PART 9: THE CUSTOM UTILITY
// ============================================
@utility tab-4 {
tab-size: 4;
}
@custom-variant dark (&:where(.dark, .dark *));
// ============================================
// PART 10: THE COMPLETE PATTERN
// ============================================
// 1. The engine scans the source files.
// 2. The extraction is the plain-text regex.
// 3. The candidates are the class strings.
// 4. The generation is the on-demand.
// 5. The theme is the @theme block.
// 6. The arbitrary value is the inline.
// 7. The dynamic class is the invisible.
// 8. The static class is the visible.
// 9. The incremental is the fast.
// 10. The output is the minimal.
The ten parts showed the source file, the extracted candidates, the generated CSS, the theme tokens, the arbitrary value, the stacked variant, the dynamic class problem, the @source directive, the custom utility, and the complete pattern.
Quick Reference
The Candidate Forms
| The form | The example |
|---|---|
| The utility | bg-blue-600 |
| The variant | hover:bg-blue-700 |
| The arbitrary value | bg-[#1da1f2] |
| The arbitrary property | [mask-type:luminance] |
| The negative | -mt-4 |
| The important | bg-red-500! |
| The stacked | dark:md:hover:bg-blue-700 |
The Scanning
| The source | The mechanism |
|---|---|
| The automatic | The .gitignore, the binary ignore |
| The Vite | The module graph |
The @source | The explicit path |
The @source not | The exclusion |
The Configuration
| The directive | The purpose |
|---|---|
@import "tailwindcss" | The framework |
@theme { } | The tokens |
@utility | The custom utility |
@custom-variant | The custom variant |
@source | The explicit path |
The Generation
| The candidate | The generated |
|---|---|
bg-blue-600 | The declaration |
hover:bg-blue-600 | The :hover |
bg-[#1da1f2] | The inline |
dark:md:hover:... | The nested |
bg-red-500! | The !important |
-mt-4 | The negative |
The Performance
| The build | The time |
|---|---|
| The v3 full | The 960ms |
| The v4 full | The 105ms |
| The v4 incremental | The ~100x faster |
Best Practices
✅ Do This:
// Use the static class names.
<div className="text-red-600"> // ✅
// Use the CSS variable for the dynamic.
<div className="text-[--accent]" style={{ '--accent': '#1da1f2' }}> // ✅
// Use the @source for the explicit paths.
@source "../../packages/ui/src/**/*.{ts,tsx}"; // ✅
// Use the @theme for the tokens.
@theme { --color-primary: oklch(0.62 0.19 260); } // ✅
// Use the @utility for the custom.
@utility tab-4 { tab-size: 4; } // ✅
// Use the arbitrary value for the one-off.
<div className="bg-[#1da1f2]"> // ✅
// Use the complete class names.
const className = isActive ? 'bg-blue-600' : 'bg-gray-200'; // ✅
❌ Don’t Do This:
// Don't construct the class with the template literal.
const className = `text-${color}-600`; // ❌ The engine cannot see
// Don't use the string concat for the variant.
const className = 'hover:' + 'bg-blue-600'; // ❌ The engine cannot see
// Don't rely on the class from the API response.
<div className={apiClassName}> // ❌ The engine cannot see
// Don't use the old safelist.
safelist: ['text-red-600']; // ❌ The v4 uses @source inline
// Don't forget the @source for the workspace.
// The engine does not scan the node_modules. // ❌
// Don't use the dynamic property.
const className = `[mask-type:${value}]`; // ❌ The engine cannot see
// Don't expect the unknown utility to generate.
<div className="my-custom-utility"> // ❌ The engine does not know
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The class does not generate | The dynamic construction | Use the static |
| The workspace not scanned | No @source | Add the path |
| The arbitrary value ignored | The wrong syntax | Use the [...] |
| The variant not applied | The wrong order | Check the composition |
| The custom utility missing | No @utility | Add the directive |
| The token undefined | No @theme | Add the token |
| The build slow | The large scan | Narrow the @source |
Real-World Examples
1. The Basic Candidate
<div className="flex items-center gap-4">
2. The Variant
<button className="hover:bg-blue-700">
3. The Arbitrary Value
<div className="bg-[#1da1f2]">
4. The Stacked Variant
<div className="dark:md:hover:bg-blue-700">
5. The Static Class
const className = color === 'red' ? 'text-red-600' : 'text-blue-600';
6. The CSS Variable
<div className="text-[--accent]" style={{ '--accent': '#1da1f2' }}>
7. The Source
@source "../../packages/ui/src/**/*.{ts,tsx}";
8. The Theme
@theme { --color-primary: oklch(0.62 0.19 260); }
9. The Custom Utility
@utility tab-4 { tab-size: 4; }
10. The Complete Pattern
<div className="flex items-center bg-blue-600 p-4 text-white">
Visual
The JIT Pipeline
┌────────────────────────────────────────────────────────────┐
│ THE JIT PIPELINE │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 1. SCANNING │ │
│ │ The source files. │ │
│ │ The .gitignore, the binary ignore, the Vite. │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 2. EXTRACTION │ │
│ │ The plain-text regex. │ │
│ │ The candidates: flex, bg-blue-600, ... │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 3. GENERATION │ │
│ │ The on-demand. │ │
│ │ The CSS for the candidates. │ │
│ └────────────────────────┬─────────────────────────┘ │
│ │ │
│ v │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 4. OUTPUT │ │
│ │ The minimal CSS. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Dynamic Class
┌────────────────────────────────────────────────────────────┐
│ THE DYNAMIC CLASS │
│ │
│ ❌ THE TEMPLATE LITERAL │
│ ┌──────────────────────────────────────────────────┐ │
│ │ const className = `text-${color}-600`; │ │
│ │ │ │
│ │ The engine reads the source as the text. │ │
│ │ It sees: `text-${color}-600` │ │
│ │ It cannot resolve the `${color}`. │ │
│ │ No CSS is generated. │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ✅ THE STATIC CLASS │
│ ┌──────────────────────────────────────────────────┐ │
│ │ const className = color === 'red' │ │
│ │ ? 'text-red-600' │ │
│ │ : 'text-blue-600'; │ │
│ │ │ │
│ │ The engine sees both strings. │ │
│ │ The CSS for both is generated. │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
The Arbitrary Value
┌────────────────────────────────────────────────────────────┐
│ THE ARBITRARY VALUE │
│ │
│ <div class="bg-[#1da1f2]"> │
│ │ │
│ v │
│ The extraction: the string 'bg-[#1da1f2]' │
│ │ │
│ v │
│ The generation: │
│ ┌──────────────────────────────────────────────────┐ │
│ │ .bg-\[\#1da1f2\] { │ │
│ │ background-color: #1da1f2; │ │
│ │ } │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ The engine parses the value and generates the declaration.│
└────────────────────────────────────────────────────────────┘
The Incremental
┌────────────────────────────────────────────────────────────┐
│ THE INCREMENTAL │
│ │
│ The file changed: src/App.jsx │
│ │ │
│ v │
│ The engine re-scans only the changed file. │
│ │ │
│ v │
│ The new candidates: the ones in the file. │
│ │ │
│ v │
│ The generation: the new CSS. │
│ │ │
│ v │
│ The browser: the style-only update. │
│ │
│ The incremental is the ~100x faster. │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| The JIT | The just-in-time generation |
| The scanning | The source detection |
| The extraction | The plain-text regex |
| The candidate | The class string |
| The generation | The on-demand |
| The dynamic class | The invisible |
| The static class | The visible |
The @source | The explicit path |
The @theme | The tokens |
| The incremental | The fast rebuild |
Key takeaways:
- The JIT engine generates the CSS on demand. It scans the source, extracts the candidates, and generates the CSS for the candidates it found. The output is the minimal, and the build is the fast.
- The extraction is the plain-text regex. The engine does not parse the JavaScript or the JSX. It reads the files as text, and the class strings are the candidates.
- The dynamic class is the invisible. The
text-${color}-600produces the string the engine cannot resolve. The static class or the CSS variable is the fix. - The arbitrary value is the inline. The
bg-[#1da1f2]produces the declaration with the value. The engine parses the value and generates the CSS. - The stacked variants produce the nested selectors. The
dark:md:hover:bg-blue-600produces the media query with the.darkancestor and the:hover. - The
@themeblock is the token source. The engine reads the block and generates the utilities from the tokens. The token is the CSS custom property. - The
@sourcedirective adds the explicit paths. The automatic detection covers the project, and the@sourceadds the workspace and the other paths. The@source notexcludes. - The incremental build is the fast. The engine re-scans only the changed files, and the new candidates are generated. The v4 reports the roughly 100x faster incremental.
Remember: The JIT engine is the modern Tailwind’s core. It scans the source, extracts the candidates, and generates the CSS on demand. The extraction is the plain-text regex, and the dynamic class is the invisible. The result is the minimal CSS, the fast build, and the on-demand generation that supports the arbitrary values and the custom utilities.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!