| |

Tailwind CSS 71 🎨 AST Code Scrapes and JIT On-Demand Utility Class Generation

⏱️ Estimated read: 13 min | |

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 candidateThe example
The plain utilitybg-blue-600
The varianthover:bg-blue-700
The arbitrary valuebg-[#1da1f2]
The arbitrary property[mask-type:luminance]
The negative-mt-4
The importantbg-red-500!
The stacked variantdark: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 patternThe extracted
class="..."The classes
className="..."The classes
tw="..."The classes
The template literalThe static parts
The string concatThe parts

The content detection

The sourceThe mechanism
The automaticThe .gitignore, the binary ignore
The ViteThe module graph
The @sourceThe explicit path
The @source notThe 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 candidateThe generated
bg-blue-600The declaration
hover:bg-blue-600The :hover selector
bg-[#1da1f2]The inline value
dark:md:hover:...The nested
bg-red-500!The !important
-mt-4The 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

ElementPurpose
The candidate cacheThe map from the candidate
The incrementalThe re-scan the changed
The @themeThe tokens
The @utilityThe custom utility
The @custom-variantThe custom variant

The build performance

The buildThe time
The v3 fullThe 960ms
The v4 fullThe 105ms
The v4 incrementalThe ~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 formThe example
The utilitybg-blue-600
The varianthover:bg-blue-700
The arbitrary valuebg-[#1da1f2]
The arbitrary property[mask-type:luminance]
The negative-mt-4
The importantbg-red-500!
The stackeddark:md:hover:bg-blue-700

The Scanning

The sourceThe mechanism
The automaticThe .gitignore, the binary ignore
The ViteThe module graph
The @sourceThe explicit path
The @source notThe exclusion

The Configuration

The directiveThe purpose
@import "tailwindcss"The framework
@theme { }The tokens
@utilityThe custom utility
@custom-variantThe custom variant
@sourceThe explicit path

The Generation

The candidateThe generated
bg-blue-600The declaration
hover:bg-blue-600The :hover
bg-[#1da1f2]The inline
dark:md:hover:...The nested
bg-red-500!The !important
-mt-4The negative

The Performance

The buildThe time
The v3 fullThe 960ms
The v4 fullThe 105ms
The v4 incrementalThe ~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

PitfallWhy It HappensFix
The class does not generateThe dynamic constructionUse the static
The workspace not scannedNo @sourceAdd the path
The arbitrary value ignoredThe wrong syntaxUse the [...]
The variant not appliedThe wrong orderCheck the composition
The custom utility missingNo @utilityAdd the directive
The token undefinedNo @themeAdd the token
The build slowThe large scanNarrow 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

ItemValue
The JITThe just-in-time generation
The scanningThe source detection
The extractionThe plain-text regex
The candidateThe class string
The generationThe on-demand
The dynamic classThe invisible
The static classThe visible
The @sourceThe explicit path
The @themeThe tokens
The incrementalThe 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}-600 produces 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-600 produces the media query with the .dark ancestor and the :hover.
  • The @theme block is the token source. The engine reads the block and generates the utilities from the tokens. The token is the CSS custom property.
  • The @source directive adds the explicit paths. The automatic detection covers the project, and the @source adds the workspace and the other paths. The @source not excludes.
  • 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.


Was this helpful?


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!