Tailwind CSS v4.2.2 — utility-first CSS framework reimagined as a full compiler
License: MIT | Source: tailwindlabs/tailwindcss @ 5a835e1
Tailwind CSS v4 is a ground-up rewrite that replaces the JavaScript-driven PostCSS plugin architecture of v3 with a standalone CSS compiler built on a Rust extraction engine, a TypeScript AST pipeline, and first-party integrations for every major build tool. This guide covers the internal architecture, build system, configuration surface, deployment patterns, security posture, and developer workflows necessary to operate, extend, and troubleshoot Tailwind CSS at scale.
1. Architecture Overview
1.1 High-Level Data Flow
Source Files (.html, .jsx, .vue, ...)
│
▼
┌─────────────────────┐
│ Oxide Scanner │ Rust (napi-rs)
│ (crates/oxide) │ State-machine candidate extraction
└────────┬────────────┘
│ candidate strings
▼
┌─────────────────────┐
│ CSS Compiler │ TypeScript
│ (packages/ │ Parse → Resolve → Compile → Optimize
│ tailwindcss) │
└────────┬────────────┘
│ CSS AST
▼
┌─────────────────────┐
│ Optimization │ lightningcss
│ Minification │ (via @tailwindcss-node)
│ Source Maps │
└────────┬────────────┘
│
▼
Output CSS file
The compiler operates in two distinct phases:
-
Scan phase — The Rust-based oxide engine traverses source files, applies language-specific pre-processors, and runs a battery of state machines to extract candidate class strings. This is CPU-bound, parallelized via
rayon, and SIMD-optimized for whitespace skipping. -
Generate phase — The TypeScript compiler receives candidate strings, resolves them against the design system (theme values, registered utilities, registered variants), applies
@applydirectives, resolves@importchains, and emits a CSS AST that gets serialized and optionally optimized throughlightningcss.
1.2 Monorepo Layout
The repository is a dual workspace: pnpm manages TypeScript/JavaScript packages; Cargo manages Rust crates. Turborepo (turbo) orchestrates cross-language builds.
tailwindcss/
├── packages/
│ ├── tailwindcss/ Core CSS compiler (TS)
│ ├── @tailwindcss-cli/ CLI binary
│ ├── @tailwindcss-postcss/ PostCSS plugin
│ ├── @tailwindcss-vite/ Vite plugin
│ ├── @tailwindcss-webpack/ Webpack loader
│ ├── @tailwindcss-node/ Node.js integration layer
│ ├── @tailwindcss-browser/ In-browser compiler
│ ├── @tailwindcss-standalone/ Standalone Bun-built binary
│ ├── @tailwindcss-upgrade/ v3→v4 migration tool
│ └── @tailwindcss-oxide/ N-API bindings for Rust
├── crates/
│ ├── oxide/ Core extraction engine (Rust)
│ ├── node/ N-API bridge (Rust)
│ ├── ignore/ .gitignore matcher (forked from ripgrep)
│ └── classification-macros/ Proc macros for byte classification
├── integrations/ Integration test suites
├── playgrounds/ Dev playgrounds
└── oxide/ Rust workspace root
1.3 Package Dependency Graph
@tailwindcss-cli ──────────┐
@tailwindcss-postcss ──────┤
@tailwindcss-vite ─────────┤
@tailwindcss-webpack ──────┤
▼
@tailwindcss-node
│
┌─────┴─────┐
▼ ▼
tailwindcss @tailwindcss-oxide
│ │
│ ┌─────┘
▼ ▼
(lightningcss, enhanced-resolve,
jiti, source-map-js)
Every build tool integration depends on @tailwindcss-node, which provides module resolution, optimization, and the bridge to the core compiler. The core tailwindcss package has zero Node.js dependencies — it is pure TypeScript operating on CSS ASTs.
1.4 Technology Stack
| Layer | Technology | Purpose |
|---|---|---|
| Class extraction | Rust + napi-rs | Performance-critical candidate parsing |
| CSS compiler | TypeScript | AST manipulation, utility generation, variant application |
| CSS optimization | lightningcss | Minification, vendor prefixing, modern CSS transforms |
| Module resolution | enhanced-resolve | CSS @import, plugin/config resolution |
| TS config loading | jiti | Load .ts config files without pre-compilation |
| Source manipulation | magic-string | Efficient string mutations with source map support |
| File watching | @parcel/watcher | Native filesystem events for --watch mode |
| Parallel scanning | rayon | Thread-pool parallelism in Rust oxide |
| Test framework | vitest + Playwright | Unit/integration tests + browser E2E |
| Build orchestration | turbo + tsup + Cargo | Monorepo task graph with caching |
| Package management | pnpm 9.6.0 | Workspace linking with strict dependency hoisting |
2. System Requirements
2.1 Build-Time Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| Node.js | 18.x | 20.x or 22.x (LTS) |
| pnpm | 9.6.0 | 9.6.0+ (version pinned in packageManager) |
| Rust | stable (edition 2021) | Latest stable via rustup |
| Cargo | Included with Rust | — |
| Python | 3.x (for node-gyp) | Only needed for native module compilation |
| OS | Linux x64/arm64, macOS x64/arm64, Windows x64 | Linux x64 for CI |
Important
The Rust toolchain is only required when building oxide from source. Published npm packages include pre-built platform binaries for 12 targets: linux-x64-gnu, linux-x64-musl, linux-arm64-gnu, linux-arm64-musl, darwin-x64, darwin-arm64, win32-x64-msvc, win32-arm64-msvc, freebsd-x64, android-arm64, android-arm-eabi, and wasm32-wasi-preview1-threads.
2.2 Runtime Requirements
For end users consuming published packages:
| Integration | Node.js | Additional |
|---|---|---|
CLI (@tailwindcss/cli) |
18+ | None |
| PostCSS plugin | 18+ | PostCSS 8.x |
| Vite plugin | 18+ | Vite 5.x or 6.x (7.x supported via Environment API) |
| Webpack loader | 18+ | Webpack 5.x |
| Standalone binary | None | Self-contained Bun executable |
| Browser build | None | Modern browser with MutationObserver |
2.3 Platform Binary Matrix
The @tailwindcss-oxide package publishes platform-specific npm packages using the napi-rs convention:
| Platform | Architecture | Package |
|---|---|---|
| Linux (glibc) | x64 | @tailwindcss/oxide-linux-x64-gnu |
| Linux (glibc) | arm64 | @tailwindcss/oxide-linux-arm64-gnu |
| Linux (musl) | x64 | @tailwindcss/oxide-linux-x64-musl |
| Linux (musl) | arm64 | @tailwindcss/oxide-linux-arm64-musl |
| macOS | x64 | @tailwindcss/oxide-darwin-x64 |
| macOS | arm64 | @tailwindcss/oxide-darwin-arm64 |
| Windows | x64 | @tailwindcss/oxide-win32-x64-msvc |
| Windows | arm64 | @tailwindcss/oxide-win32-arm64-msvc |
| FreeBSD | x64 | @tailwindcss/oxide-freebsd-x64 |
| Android | arm64 | @tailwindcss/oxide-android-arm64 |
| Android | arm (eabi) | @tailwindcss/oxide-android-arm-eabi |
| WASM | wasi | @tailwindcss/oxide-wasm32-wasi-preview1-threads |
Warning
Linux ARM64 users have reported missing native module errors when the correct platform package is not installed. This commonly occurs in Docker multi-arch builds or when using npm instead of pnpm (which handles optional dependencies differently). See GitHub issue #15806. Ensure your package manager installs optional dependencies, or use the standalone binary as a workaround.
2.4 Standalone Binary
The standalone binary (@tailwindcss/standalone) bundles the full compiler into a single executable built with Bun. It includes the official plugins (@tailwindcss/forms, @tailwindcss/typography, @tailwindcss/aspect-ratio) and requires no Node.js runtime.
Platform binaries:
tailwindcss-linux-x64tailwindcss-linux-arm64tailwindcss-macos-x64tailwindcss-macos-arm64tailwindcss-windows-x64.exe
Module resolution in standalone mode uses globalThis.__tw_resolve and globalThis.__tw_load — custom shims that resolve against the bundled module graph rather than the filesystem.
3. Configuration Reference
3.1 CSS-First Configuration (v4 Model)
Tailwind CSS v4 replaces tailwind.config.js with CSS-native configuration. All customization happens inside your CSS file using at-rules:
@import "tailwindcss";
/* Theme customization */
@theme {
--color-primary: #3b82f6;
--font-sans: "Inter", sans-serif;
--breakpoint-3xl: 1920px;
}
/* Source detection */
@source "../components/**/*.tsx";
/* Plugin loading */
@plugin "@tailwindcss/forms";
/* Custom variant */
@variant dark (&:where(.dark, .dark *));
3.2 CSS At-Rules Reference
| At-Rule | Purpose | Example |
|---|---|---|
@import "tailwindcss" |
Load the full framework (preflight + theme + utilities) | @import "tailwindcss" |
@theme |
Define or override design tokens | @theme { --color-brand: #f00; } |
@theme inline |
Define theme values that inline rather than emit CSS variables | @theme inline { --color-*: initial; } |
@theme reference |
Import theme for tooling without emitting CSS | @theme reference { ... } |
@source |
Add glob patterns for candidate scanning | @source "../app/**/*.html" |
@source not |
Exclude paths from scanning | @source not "./legacy/**" |
@plugin |
Load a JS/TS plugin | @plugin "./my-plugin.ts" |
@config |
Load a JS/TS config file (v3 compat) | @config "./tailwind.config.ts" |
@variant |
Register a custom variant | @variant hocus (&:hover, &:focus) |
@custom-variant |
Alias for @variant |
Same syntax |
@utility |
Define a custom utility | @utility tab-* { tab-size: --value(--tab-*); } |
@apply |
Inline utility classes | .btn { @apply px-4 py-2 rounded; } |
@reference |
Import for reference only (no output) | @reference "tailwindcss" |
@tailwind |
Legacy directive (v3 compat, mapped internally) | @tailwind utilities |
Note
The
@import "tailwindcss"directive is the canonical entry point. It expands to@import "tailwindcss/preflight.css",@import "tailwindcss/theme.css", and@import "tailwindcss/utilities.css". You can import these individually for granular control.
3.3 Theme Namespace Reference
Theme values are CSS custom properties organized by namespace. Each namespace maps to a set of utilities:
| Namespace | Utilities | Example Token |
|---|---|---|
--color-* |
text-*, bg-*, border-*, ring-*, etc. |
--color-blue-500: #3b82f6 |
--font-* |
font-* |
--font-sans: "Inter", sans-serif |
--font-size-* |
text-* |
--font-size-lg: 1.125rem |
--font-weight-* |
font-* |
--font-weight-bold: 700 |
--tracking-* |
tracking-* |
--tracking-tight: -0.025em |
--leading-* |
leading-* |
--leading-relaxed: 1.625 |
--breakpoint-* |
Responsive variants (sm:, md:, etc.) |
--breakpoint-md: 768px |
--spacing-* |
p-*, m-*, gap-*, w-*, h-*, etc. |
--spacing-4: 1rem |
--radius-* |
rounded-* |
--radius-lg: 0.5rem |
--shadow-* |
shadow-* |
--shadow-md: 0 4px 6px ... |
--inset-shadow-* |
inset-shadow-* |
--inset-shadow-sm: inset 0 1px ... |
--drop-shadow-* |
drop-shadow-* |
--drop-shadow-lg: 0 10px 8px ... |
--blur-* |
blur-* |
--blur-md: 12px |
--width-* |
w-* |
--width-prose: 65ch |
--z-* |
z-* |
--z-50: 50 |
--opacity-* |
opacity-* |
--opacity-50: 0.5 |
--ease-* |
ease-* (transition-timing-function) |
--ease-in-out: cubic-bezier(...) |
--animate-* |
animate-* |
--animate-spin: spin 1s linear infinite |
--container-* |
@container queries |
--container-3xl: 1536px |
3.4 CSS Functions
Tailwind v4 provides CSS functions that resolve against the design system:
| Function | Purpose | Example |
|---|---|---|
--theme(...) |
Reference a theme value by its token path | color: --theme(--color-blue-500) |
--spacing(...) |
Reference the spacing scale | margin: --spacing(4) |
--alpha(color, opacity) |
Apply alpha to a theme color | color: --alpha(--color-blue-500, 50%) |
Caution
--spacing()requires an argument. Calling it without one produces the error: "The --spacing(...) function requires an argument" (css-functions.ts:49). Similarly,--theme()only works with CSS variables from your theme — using it with arbitrary values produces: "The --theme(...) function can only be used with CSS variables from your theme" (css-functions.ts:75).
3.5 Environment Variables
| Variable | Package | Purpose |
|---|---|---|
DEBUG |
@tailwindcss-node |
Enable debug logging when set (checked in env.ts) |
NODE_ENV |
@tailwindcss-postcss |
When production, enables optimization passes |
NODE_PATH |
@tailwindcss-node |
Additional module resolution paths (passed to enhanced-resolve) |
3.6 Build Tool Configuration
CLI Options
tailwindcss build [options]
Options:
-i, --input <path> Input CSS file (use "-" for stdin)
-o, --output <path> Output CSS file (use "-" for stdout)
-w, --watch Watch for changes and rebuild
--minify Minify the output CSS
-h, --help Show help
Commands:
build Build CSS (default)
canonicalize Output sorted/canonical CSS
Tip
The CLI supports stdin/stdout piping for CI integration:
cat input.css | tailwindcss build -i - -o -. Thecanonicalizecommand is useful for diffing CSS output across versions — it produces a deterministic sort order.
PostCSS Plugin
// postcss.config.js
module.exports = {
plugins: {
"@tailwindcss/postcss": {
// Options (all optional):
base: "./src", // Base directory for file resolution
optimize: {
minify: true // Enable CSS minification
}
}
}
};
The PostCSS plugin maintains an LRU cache of compiled CSS roots. It performs incremental rebuilds when source files change and full rebuilds when the CSS input itself changes. AST conversion between PostCSS nodes and Tailwind's internal AST happens bidirectionally.
Vite Plugin
// vite.config.ts
import tailwindcss from "@tailwindcss/vite";
export default {
plugins: [
tailwindcss({
// Options (all optional):
optimize: {
minify: true // Enable CSS minification (defaults to mode-based)
}
})
]
};
Two-step architecture:
- Scan step (
enforce: "pre") — intercepts CSS imports, registers them for processing, injects module graph tracking. - Generate step (no enforce) — compiles CSS with extracted candidates, handles HMR updates.
The plugin supports the Vite Environment API (Vite 7+) for SSR-aware builds. CSS changes trigger HMR updates; template changes that introduce new candidates trigger a full page reload.
Webpack Loader
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
{
loader: "@tailwindcss/webpack",
options: {
base: "./src", // Base directory
optimize: {
minify: true // Enable minification
}
}
}
]
}
]
}
};
The Webpack loader uses an LRU cache (max 50 entries) with mtime-based invalidation. It performs a quick bail check on CSS files — if the content contains none of @import, @reference, @theme, @variant, @config, @plugin, @apply, or @tailwind, it skips processing entirely. CSS Modules are supported; when detected, the @property polyfill is disabled to avoid conflicts.
4. Deployment & Scaling
4.1 Integration Patterns
| Pattern | Tool | When to Use |
|---|---|---|
| Vite plugin | @tailwindcss/vite |
Vite-based projects (recommended for new projects) |
| PostCSS plugin | @tailwindcss/postcss |
Existing PostCSS pipelines, Next.js, Nuxt |
| Webpack loader | @tailwindcss/webpack |
Webpack 5 projects |
| CLI | @tailwindcss/cli |
Framework-agnostic, CI/CD pipelines |
| Standalone | @tailwindcss/standalone |
No Node.js runtime available, Docker scratch images |
| Browser | @tailwindcss/browser |
Playgrounds, CodePen, prototyping |
4.2 CI/CD Build Optimization
Caching strategy:
The oxide scanner and CSS compiler are deterministic — same inputs always produce the same outputs. Cache the following:
- Node modules —
node_modules/.pnpm/(or equivalent for your package manager) - Turbo cache —
.turbo/if building from source - Standalone binary — download once per version, cache across builds
Build performance tips:
- The Rust oxide scanner is the fastest path. Avoid falling back to JS-based scanning.
- Use
@sourcedirectives to limit the scanning scope. The auto-detection scans everything not in.gitignore, which can be expensive in large monorepos. - The
@source notdirective prunes entire directory trees from scanning. - For Docker builds, use multi-stage builds and copy only source files needed for CSS compilation.
4.3 Docker Considerations
# Multi-stage build example
FROM node:20-slim AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm tailwindcss build -i src/input.css -o dist/output.css --minify
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
Warning
Docker bind mounts can cause issues with the CLI's file watcher (
@parcel/watcher). The native filesystem events may not propagate correctly through bind mounts on some host OS/Docker runtime combinations. See GitHub issue #18540. Use polling mode or volume mounts as a workaround.
4.4 Source Detection and Auto-Content
The oxide scanner performs automatic source detection:
- Starts from the CSS file's directory (or project root).
- Walks the directory tree, respecting
.gitignorepatterns. - Skips binary file extensions,
node_modules,.git, and other known non-source directories. - Applies language-specific pre-processors to extract class candidates from templates.
Pre-processors by file type:
| Language/Format | Pre-processing |
|---|---|
| Clojure | Keyword extraction from hiccup syntax |
| Elixir | HEEx template handling |
| Haml | Attribute extraction |
| JSON | String value extraction |
| Markdown | Fenced code block handling |
| Pug | Class attribute extraction |
| Razor | .cshtml attribute handling |
| Ruby | ERB template handling |
| Rust | Macro string extraction |
| Slim | Attribute extraction |
| Svelte | Template + script handling |
| Vue | SFC <template> + <script> handling |
Ignore patterns (built-in):
The scanner automatically ignores files with these extensions: .avif, .bmp, .csv, .gif, .ico, .jar, .jpeg, .jpg, .lock, .map, .mp3, .mp4, .ogg, .otf, .pdf, .png, .svg, .ttf, .wav, .webm, .webp, .woff, .woff2, .zip (and others). Directories like node_modules, .git, .svn, .hg are also skipped.
5. Operations
5.1 Build Modes
Full build — Processes all CSS from scratch. Occurs on first build or when the CSS input file changes structurally (new @theme, @plugin, @import directives).
Incremental build — Only reprocesses changed candidates. Occurs when source files (templates, components) change but the CSS configuration remains the same. The PostCSS plugin, Vite plugin, and Webpack loader all support incremental builds.
Watch mode — Available via CLI (--watch) and implicitly in Vite/Webpack dev servers. Uses @parcel/watcher for native filesystem events. Triggers incremental or full rebuilds as appropriate.
5.2 Diagnostic Output and Debugging
Set the DEBUG environment variable to enable verbose logging from @tailwindcss-node:
DEBUG=1 tailwindcss build -i input.css -o output.css
Common error messages and their causes:
| Error | Source | Cause |
|---|---|---|
Cannot apply unknown utility class |
apply.ts:242 |
@apply references a class that doesn't exist in the design system |
You cannot use @apply inside @keyframes |
apply.ts:36 |
@apply used inside a @keyframes block (not supported) |
Circular dependency detected in custom variants |
index.ts:639 |
Variant A references variant B which references variant A |
Missing opening { |
css-parser.ts:427 |
Malformed CSS — unbalanced braces |
Invalid theme value |
theme.ts:63 |
Theme token references a value that doesn't exist |
Could not resolve '{id}' from '{base}' |
compile.ts:127,140,165 |
Module resolution failed for @import, @plugin, or @config |
The browser build does not support @import |
browser/index.ts:149 |
External @import used in browser build |
The browser build does not support plugins or config files |
browser/index.ts:174 |
@plugin or @config used in browser build |
5.3 Migration from v3 to v4
The @tailwindcss/upgrade package provides an automated migration tool:
npx @tailwindcss/upgrade
What it migrates:
| Area | Transformations |
|---|---|
| CSS directives | @tailwind base/components/utilities → @import "tailwindcss" |
| CSS imports | Layer-based imports → standard imports |
@apply |
Updates class names to v4 equivalents |
| Theme references | theme('colors.blue.500') → var(--color-blue-500) or --theme(--color-blue-500) |
| Preflight | Updates preflight references |
| Variants | Migrates deprecated variant syntax |
| Template classes | Updates renamed/removed utility classes |
| Arbitrary values | Updates deprecated arbitrary value syntax |
| Legacy classes | Maps v3 classes to v4 equivalents |
| JS config | Migrates tailwind.config.js values into CSS @theme blocks |
| PostCSS config | Updates PostCSS configuration for v4 plugin |
| Prefix migration | Translates tw- prefix patterns to v4 approach |
The upgrade tool uses tree-sitter and tree-sitter-typescript for robust template parsing — it does not rely on regex for JavaScript/TypeScript source transformations.
5.4 Monitoring Build Health
Key metrics to track in production build pipelines:
- Build time — Full builds should take <2s for typical projects. >5s indicates excessive source scanning scope.
- Output size — Minified CSS should be proportional to utility usage. Sudden size jumps indicate possible
@applyproliferation or theme bloat. - Candidate count — Monitor how many unique candidates the scanner extracts. False positives (strings that look like classes but aren't) increase compile time without affecting output.
- Source map validity — SOURCEMAP_BROKEN warnings (see issue #19930) can appear in Nuxt/Vite setups due to source map chain breaks.
6. Performance
6.1 Oxide Scanner Architecture
The Rust oxide engine is the performance-critical path. It uses a hierarchy of state machines to extract candidate class strings from arbitrary source files:
Input bytes
│
├─► Language pre-processor (optional)
│
├─► Fast skip (SIMD whitespace detection)
│
└─► State machine cascade:
├── CandidateMachine (top-level dispatcher)
│ ├── UtilityMachine (e.g., "bg-blue-500")
│ ├── VariantMachine (e.g., "hover:")
│ ├── NamedUtilityMachine (e.g., "text-lg")
│ ├── NamedVariantMachine (e.g., "group-hover:")
│ ├── ArbitraryPropertyMachine (e.g., "[color:red]")
│ ├── ArbitraryValueMachine (e.g., "bg-[#f00]")
│ ├── ArbitraryVariableMachine (e.g., "bg-(--my-var)")
│ ├── CssVariableMachine (e.g., "--tw-bg-opacity")
│ ├── ModifierMachine (e.g., "/50")
│ └── StringMachine (quoted strings)
│
└─► Extracted candidates (deduplicated)
Key optimizations:
- SIMD fast-skip — Uses SIMD instructions to skip over large whitespace regions without entering state machines.
- Byte classification macros — The
classification-macroscrate provides#[bytes(...)]and#[bytes_range(...)]proc macros that generate lookup tables for O(1) byte classification. - Rayon parallelism — File scanning is parallelized across CPU cores via
rayon's work-stealing thread pool. - Glob hoisting — The scanner optimizes glob patterns by hoisting common prefixes, reducing directory traversal.
- Language pre-processors — Strip non-relevant syntax before candidate extraction, reducing false positives and improving throughput.
6.2 N-API Bridge
The @tailwindcss-oxide package exposes the Rust scanner to Node.js via napi-rs:
class Scanner {
constructor(options: {
sources: Array<{ base: string; pattern?: string }>;
globs?: Array<{ origin?: string; base: string; pattern: string }>;
})
scan(): string[] // Scan all sources, return candidates
scan_files(files: string[]): string[] // Scan specific files
files(): string[] // List discovered source files
globs(): Array<{ base: string; pattern: string }> // Active glob patterns
normalized_sources(): Array<{ base: string; pattern: string }>
}
The N-API bridge in crates/node handles type conversion between Rust and JavaScript. Scanned results are deduplicated in Rust before crossing the N-API boundary to minimize serialization overhead.
6.3 CSS Compiler Performance
The TypeScript compiler processes candidates through several stages:
- Candidate parsing — Each candidate string is parsed into a structured
Candidatetype (static, functional, or arbitrary). - Utility resolution — Candidates are matched against registered utilities. Theme values are resolved lazily.
- Variant application — Variants are applied as wrappers around the resolved utility CSS.
- AST construction — CSS AST nodes are created and deduplicated.
- @apply expansion — Inline utility references are expanded recursively.
- Serialization — The AST is serialized to a CSS string.
Incremental compilation reuses the design system state and only processes new candidates. The LRU caches in PostCSS (unbounded) and Webpack (max 50) store compiled results keyed by input CSS content hash.
6.4 lightningcss Optimization
The @tailwindcss-node package applies lightningcss for:
- Minification — Whitespace removal, shorthand collapsing, value optimization.
- Vendor prefixing — Based on browserslist targets.
- Modern CSS transforms — Lowers newer CSS features for older browsers.
- Nesting flattening — Resolves CSS nesting for browsers that don't support it.
@propertypolyfill — Generates@propertyrules for registered custom properties (disabled in CSS Modules mode).
6.5 Performance Bottlenecks
Common bottlenecks in order of impact:
- Source scanning scope — Scanning too many files is the #1 performance issue. Use
@sourceto limit scope. @applychains — Each@applytriggers a mini-compilation. Deep chains compound.- Plugin count — Each plugin runs synchronously during compilation.
- Import resolution — Deep
@importchains require filesystem I/O for each resolution. - Source map generation — Adds ~10-20% overhead. Disable in production if not needed.
7. Security
7.1 Supply Chain Security
Published packages:
- All packages are published to npm under the
@tailwindcssscope (owned by Tailwind Labs). - Platform-specific oxide binaries are published as separate packages with
optionalDependencies— npm/pnpm downloads only the binary for the current platform. - The standalone binary is self-contained; no runtime dependency resolution occurs.
Native binary provenance:
The oxide binaries are compiled in CI from the crates/ source. The napi-rs build system cross-compiles for all 12 targets. The binary integrity is verified by npm's content-addressable storage (integrity hashes in lockfiles).
Caution
When using the standalone binary outside of npm (e.g., downloaded directly from GitHub releases), verify the binary hash against the published checksums. The standalone binary bundles
@tailwindcss/forms,@tailwindcss/typography, and@tailwindcss/aspect-ratio— ensure these are versions you trust.
7.2 Configuration Injection Vectors
| Vector | Risk | Mitigation |
|---|---|---|
@plugin directive |
Executes arbitrary JS/TS | Only use trusted plugins. Plugin code runs with full Node.js permissions. |
@config directive |
Loads arbitrary JS/TS config | Same as plugin — runs with full process permissions. |
| Arbitrary values in classes | CSS injection via [content:'...'] |
Tailwind sanitizes values, but complex expressions should be audited. |
@import resolution |
Path traversal | enhanced-resolve follows symlinks and can resolve outside project root. |
| Template scanning | Information disclosure | Scanner reads file contents. In CI, limit source paths to prevent scanning secrets. |
Warning
The
@pluginand@configdirectives execute arbitrary JavaScript with the same permissions as the Node.js process. In CI environments, treat CSS files as code — a malicious@plugin "./exfil.js"directive in a CSS file could exfiltrate secrets. Apply the same review rigor to CSS files as you would topackage.jsonscripts.
7.3 CSS Injection Considerations
Tailwind's utility-class approach reduces traditional CSS injection risks because:
- No runtime CSS generation — CSS is compiled at build time, not at request time.
- Deterministic output — Same input always produces same output; no user input influences CSS at runtime.
- Arbitrary value sanitization — The compiler validates arbitrary values (e.g.,
bg-[url('...')]) during parsing.
However, the browser build (@tailwindcss/browser) compiles CSS at runtime in the client. In this mode:
- Class attributes from the DOM are used as compiler input.
- A malicious DOM mutation could inject unexpected CSS.
- The browser build should only be used for trusted content (prototyping, documentation, playgrounds).
Important
Never use
@tailwindcss/browserin production applications where untrusted content can modify DOM class attributes. The MutationObserver-based scanning means any DOM change triggers recompilation with the new class values.
7.4 Content Security Policy (CSP)
The browser build injects <style> elements dynamically, requiring style-src 'unsafe-inline' or a nonce-based CSP. For build-time compilation (CLI, Vite, PostCSS, Webpack), no special CSP headers are needed — the output is a static CSS file.
7.5 Dependency Audit Surface
Core runtime dependencies (minimal attack surface):
| Package | Purpose | Risk Profile |
|---|---|---|
lightningcss |
CSS optimization | Rust binary, Mozilla-maintained |
enhanced-resolve |
Module resolution | Webpack team, filesystem access |
jiti |
TS config loading | Executes code, import() with fallback |
@parcel/watcher |
File watching (CLI only) | Native binary, Parcel team |
magic-string |
Source manipulation | Pure JS, well-audited |
source-map-js |
Source map support | Pure JS, Mozilla fork |
The core tailwindcss package has zero runtime npm dependencies. All dependencies come from the integration packages (@tailwindcss-node, @tailwindcss-cli, etc.).
8. Developer Internals
8.1 Core Compiler (packages/tailwindcss)
The core package (55 files, ~1.1M characters) is the heart of the framework. It exports three primary entry points:
// Main compilation entry
function compile(
css: string,
options?: { base?: string; from?: string }
): Promise<{
globs: Glob[];
build(candidates: string[]): string;
buildWithMaps(candidates: string[]): { css: string; map: SourceMap };
}>
// AST-level compilation
function compileAst(
ast: AstNode[],
options?: { base?: string; from?: string }
): Promise<{
globs: Glob[];
build(candidates: string[]): string;
}>
// Design system introspection (IntelliSense, tooling)
function __unstable__loadDesignSystem(
css: string,
options?: { base?: string }
): Promise<DesignSystem>
Note
The
__unstable__loadDesignSystemexport is used by the Tailwind CSS IntelliSense VS Code extension and other tooling. Despite the__unstable__prefix, it is actively maintained and versioned alongside the compiler. It provides the full design system state including all registered utilities, variants, and theme values.
8.2 Key Internal Types
DesignSystem
The central object that holds all compiler state:
interface DesignSystem {
theme: Theme
utilities: Map<string, UtilityDefinition>
variants: Map<string, VariantDefinition>
// Internal compiler state
features: Features
polyfills: Polyfills
}
Theme
The theme is a CSS custom property store with namespace awareness:
class Theme {
// Resolve a theme value by its full path
resolve(path: string): string | undefined
// Resolve with namespace awareness (e.g., "--color-" namespace)
resolveNamespaced(namespace: string, key: string): string | undefined
// Check if a namespace has any values
hasNamespace(namespace: string): boolean
}
AstNode
The CSS AST is a union type:
type AstNode =
| { kind: "rule"; selector: string; nodes: AstNode[] }
| { kind: "at-rule"; name: string; params: string; nodes: AstNode[] }
| { kind: "declaration"; property: string; value: string; important: boolean }
| { kind: "comment"; value: string }
| { kind: "context"; context: Record<string, string>; nodes: AstNode[] }
| { kind: "at-root"; nodes: AstNode[] }
Features (Bitflags)
enum Features {
None = 0,
AtApply = 1 << 0, // @apply directives present
JsPluginCompat = 1 << 1, // JS plugin compatibility mode
ThemeFunction = 1 << 2, // --theme() function used
Utilities = 1 << 3 // Utility generation needed
}
Polyfills (Bitflags)
enum Polyfills {
All = ~0,
AtProperty = 1 << 0 // @property rule generation
}
Candidate
Parsed utility class representation:
type Candidate =
| { kind: "static"; value: string; variants: Variant[]; important: boolean }
| { kind: "functional"; value: string; modifier?: string; variants: Variant[]; important: boolean }
| { kind: "arbitrary"; property: string; value: string; variants: Variant[]; important: boolean }
Variant
type Variant =
| { kind: "static"; value: string }
| { kind: "functional"; value: string; modifier?: string }
| { kind: "compound"; value: string; inner: Variant }
| { kind: "arbitrary"; selector: string }
8.3 CSS Parser (css-parser.ts)
The custom CSS parser handles Tailwind-specific extensions while maintaining CSS spec compliance. It produces AstNode[] from raw CSS strings.
Key behaviors:
- Handles
@theme,@plugin,@config,@source,@variant,@utility,@apply,@referenceas first-class at-rules. - Preserves comments in the AST for source map fidelity.
- Reports structured errors with line/column information (e.g.,
Missing opening {atcss-parser.ts:427). - Supports CSS nesting syntax natively.
8.4 Utility Registration and Resolution
Utilities are registered via the design system, either from the built-in set or via plugins:
// Static utility (fixed CSS output)
designSystem.utilities.set("sr-only", {
// CSS declarations
});
// Functional utility (value-parameterized)
designSystem.utilities.set("bg-*", {
// Resolve value from theme or arbitrary, generate CSS
});
The resolution process:
- Parse candidate string into
Candidatestructure. - Look up utility by name in the registry.
- For functional utilities, resolve the value against the theme namespace.
- Apply any modifier (e.g.,
/50for opacity). - Wrap in variant selectors (applied outside-in).
- Return CSS AST nodes.
8.5 Variant System
Variants transform utility selectors. They are composable (stackable) and ordered by specificity:
Built-in variant categories:
- Pseudo-class:
hover,focus,active,visited,first-child,last-child, etc. - Pseudo-element:
before,after,placeholder,file,marker,selection, etc. - Responsive:
sm,md,lg,xl,2xl(from--breakpoint-*theme) - Dark mode:
dark(configurable: media query or class-based) - Container queries:
@sm,@md,@lg, etc. - State:
open,closed,disabled,enabled,checked,indeterminate, etc. - Group/peer:
group-hover,peer-focus, etc. - Arbitrary:
[&:nth-child(3)],[@media(hover:hover)], etc.
Circular dependency detection (index.ts:639) prevents infinite loops in custom variant definitions.
8.6 @apply Implementation (apply.ts)
The @apply directive is one of the most complex features. It works by:
- Collecting all
@applydirectives in the AST. - For each directive, parsing the referenced utility classes.
- Compiling each utility against the design system.
- Inlining the resulting declarations at the
@applysite. - Handling
!importantpropagation. - Detecting and reporting circular
@applychains.
Restrictions:
- Cannot be used inside
@keyframesblocks (apply.ts:36). - Cannot reference unknown utilities (
apply.ts:242). - Recursive
@applyis supported but circular references are detected and rejected.
8.7 @import Resolution
The compiler resolves @import directives using a chain of resolvers provided by @tailwindcss-node:
- CSS resolver — Resolves bare specifiers against
node_moduleswith CSS-specific extensions (.css). - ESM resolver — Resolves with ESM conditions (
import,module). - CJS resolver — Fallback to CommonJS resolution (
require,main).
All resolvers are built on enhanced-resolve with appropriate configuration. Resolution respects NODE_PATH environment variable for additional search paths.
URL rewriting ensures that relative URLs in imported CSS files are adjusted relative to the output file, not the source file.
8.8 Module Reference
packages/tailwindcss — Core Compiler
| File | Purpose |
|---|---|
index.ts |
Main entry: compile(), compileAst(), __unstable__loadDesignSystem(). Design system initialization, plugin loading, variant/utility registration. |
css-parser.ts |
Custom CSS parser producing AstNode[]. Handles all Tailwind at-rules. |
ast.ts |
AST node type definitions and manipulation utilities. |
theme.ts |
Theme class — CSS custom property store with namespace resolution. |
candidate.ts |
Candidate parsing — converts class strings to structured Candidate objects. |
apply.ts |
@apply directive expansion with circular reference detection. |
utilities.ts |
Built-in utility registration (all core utilities). |
variants.ts |
Built-in variant registration (pseudo-classes, responsive, dark, etc.). |
css-functions.ts |
--theme(), --spacing(), --alpha() function resolution. |
source-maps.ts |
Source map generation and manipulation using source-map-js. |
compat/ |
v3 compatibility layer: plugin API shim, config resolution. |
intellisense.ts |
IntelliSense support: class list generation, completion data. |
preflight.css |
CSS reset/normalize (Preflight). |
theme.css |
Default theme values as CSS custom properties. |
utilities.css |
Utility layer entry point. |
index.css |
Main CSS entry: imports preflight + theme + utilities. |
packages/@tailwindcss-node — Node.js Integration
| File | Purpose |
|---|---|
src/compile.ts |
compile() wrapper that integrates module resolution, URL rewriting, and optimization. Three resolver types: CSS, ESM, CJS. |
src/optimize.ts |
lightningcss integration for minification and transforms. |
src/env.ts |
DEBUG environment variable check. |
src/urls.ts |
URL rewriting for @import-ed CSS files. |
src/source-maps.ts |
Source map chain management. |
packages/@tailwindcss-cli — CLI Tool
| File | Purpose |
|---|---|
src/index.ts |
Entry point, argument parsing via mri. Command dispatch. |
src/commands/build/index.ts |
build command: compile CSS, write output, optional watch mode. |
src/commands/canonicalize/index.ts |
canonicalize command: deterministic CSS sorting. |
packages/@tailwindcss-postcss — PostCSS Plugin
| File | Purpose |
|---|---|
src/index.ts |
PostCSS plugin factory. LRU cache, incremental rebuild logic, AST conversion between PostCSS and internal formats. |
packages/@tailwindcss-vite — Vite Plugin
| File | Purpose |
|---|---|
src/index.ts |
Vite plugin factory. Two-step scan/generate architecture. HMR handling. Vite Environment API support for v7+. |
packages/@tailwindcss-webpack — Webpack Loader
| File | Purpose |
|---|---|
src/index.ts |
Webpack loader with LRU cache (max 50), mtime invalidation, CSS Modules detection. Quick bail check for non-Tailwind CSS files. |
packages/@tailwindcss-browser — Browser Build
| File | Purpose |
|---|---|
src/index.ts |
In-browser compiler. Watches <style type="text/tailwindcss"> elements. MutationObserver on class attributes, new elements, new stylesheets. Queue-based build system (full/incremental). |
packages/@tailwindcss-oxide — N-API Bindings
| File | Purpose |
|---|---|
src/index.ts |
Scanner class TypeScript types. Platform binary loading with fallback. |
| Native binaries | Platform-specific .node files for 12 targets. |
packages/@tailwindcss-upgrade — Migration Tool
| File | Purpose |
|---|---|
src/index.ts |
Entry point, project detection, migration orchestration. |
src/codemods/css/ |
CSS-level codemods: directives, imports, layers, @apply, theme-to-var, preflight, variants. |
src/codemods/template/ |
Template-level codemods: candidates, arbitrary values/variants, legacy classes, prefix migration. |
src/codemods/js/ |
JS config migration to CSS @theme. |
src/codemods/postcss/ |
PostCSS config updates. |
crates/oxide — Rust Extraction Engine
| Module | Purpose |
|---|---|
src/lib.rs |
Public API: Scanner struct, scan(), scan_files(), files(), globs(). |
src/scanner/ |
State machine implementations: CandidateMachine, UtilityMachine, VariantMachine, NamedUtilityMachine, NamedVariantMachine, ArbitraryPropertyMachine, ArbitraryValueMachine, ArbitraryVariableMachine, CssVariableMachine, ModifierMachine, StringMachine. |
src/pre_processors/ |
Language-specific pre-processors for Clojure, Elixir, Haml, JSON, Markdown, Pug, Razor, Ruby, Rust, Slim, Svelte, Vue. |
src/glob.rs |
Glob pattern matching with hoisting optimization. |
src/fast_skip.rs |
SIMD-optimized whitespace skipping. |
src/cursor.rs |
Byte cursor for state machine input. |
crates/node — N-API Bridge
| Module | Purpose |
|---|---|
src/lib.rs |
napi-rs bindings. Scanner class exposed to Node.js. Uses rayon for parallel file scanning. |
crates/ignore — Gitignore Matching
| Module | Purpose |
|---|---|
src/lib.rs |
.gitignore pattern matching engine. Parallel directory walker. Forked from ripgrep's ignore crate. |
crates/classification-macros — Proc Macros
| Module | Purpose |
|---|---|
src/lib.rs |
#[bytes(...)] and #[bytes_range(...)] proc macros for generating byte lookup tables used by the scanner state machines. |
8.9 Compilation Pipeline Detail
The full compilation pipeline from CSS input to CSS output:
1. CSS Input String
│
2. ├─► CSS Parser (css-parser.ts)
│ └─► AstNode[]
│
3. ├─► @import Resolution (@tailwindcss-node/compile.ts)
│ └─► Recursive: resolve path → read file → parse → inline AST
│
4. ├─► Design System Initialization (index.ts)
│ ├─► Process @theme → populate Theme
│ ├─► Process @plugin → load JS, register utilities/variants
│ ├─► Process @config → load JS config, merge
│ ├─► Process @variant → register custom variants
│ ├─► Process @utility → register custom utilities
│ ├─► Process @source → configure oxide Scanner globs
│ └─► Register built-in utilities + variants
│
5. ├─► Oxide Scan (via Scanner.scan())
│ └─► candidate strings[]
│
6. ├─► build(candidates) called
│ ├─► Parse candidates → Candidate[]
│ ├─► Resolve each Candidate against DesignSystem
│ ├─► Apply variants (outside-in wrapping)
│ ├─► Expand @apply directives
│ └─► Produce final AstNode[]
│
7. ├─► Serialize AST → CSS string
│
8. ├─► Source map generation (optional)
│
9. └─► lightningcss optimization (optional, via @tailwindcss-node)
├─► Minification
├─► Vendor prefixing
├─► @property polyfill
└─► Final CSS output
8.10 Code Patterns and Conventions
State machine pattern (Rust oxide):
Every scanner machine implements a common interface: accept a byte cursor, advance through states, and return either a matched span or a rejection. Machines are composable — CandidateMachine dispatches to child machines based on the first few bytes. This avoids backtracking and keeps extraction O(n) in file size.
Bitflag feature detection (TypeScript):
The Features and Polyfills enums use bitwise operations for zero-cost feature detection during compilation. The compiler checks features & Features.AtApply to decide whether to run the @apply expansion pass, avoiding unnecessary work.
LRU caching (PostCSS, Webpack):
Both the PostCSS plugin and Webpack loader use LRU caches to avoid recompilation. The PostCSS cache uses @alloc/quick-lru (unbounded by default). The Webpack loader caps at 50 entries. Cache keys are derived from CSS content hashes plus file modification times.
Atomic incremental builds:
The build() function returned by compile() is designed to be called repeatedly with growing candidate sets. Internally, it tracks which candidates have already been compiled and only processes new ones. This is the foundation of incremental rebuilds in all integrations.
Module resolution cascade:
The triple-resolver pattern in @tailwindcss-node/compile.ts (CSS → ESM → CJS) ensures compatibility with the full npm ecosystem. CSS files use style and css conditions; ESM uses import and module; CJS uses require and main. This handles edge cases like packages that only export CJS or use non-standard fields.
9. Integration Points
9.1 Plugin API
Tailwind CSS v4 supports JavaScript/TypeScript plugins loaded via @plugin:
@plugin "./my-plugin.ts";
// my-plugin.ts
import plugin from "tailwindcss/plugin";
export default plugin(function ({ addUtilities, addVariant, matchUtilities, theme }) {
addUtilities({
".content-auto": { "content-visibility": "auto" },
});
matchUtilities(
{ "tab": (value) => ({ "tab-size": value }) },
{ values: theme("tabSize") }
);
addVariant("hocus", "&:hover, &:focus");
});
Tip
Plugins loaded via
@pluginare resolved relative to the CSS file, not the project root. Use@configif you need to load a full v3-style configuration object with plugin arrays.
9.2 IntelliSense Integration
The __unstable__loadDesignSystem() export provides everything the Tailwind CSS IntelliSense VS Code extension needs:
- Complete list of registered utilities with their CSS output.
- Complete list of registered variants with their selector transformations.
- Full theme value map for autocomplete suggestions.
- Candidate validation for diagnostics.
9.3 PostCSS Ecosystem
The @tailwindcss/postcss plugin operates as a standard PostCSS plugin and can be composed with other PostCSS plugins:
module.exports = {
plugins: {
"postcss-import": {}, // Runs before Tailwind
"@tailwindcss/postcss": {}, // Tailwind compilation
"autoprefixer": {} // Runs after (usually redundant — lightningcss handles this)
}
};
Important
Unlike v3, Tailwind CSS v4's PostCSS plugin handles
@importresolution internally. Usingpostcss-importalongside@tailwindcss/postcsscan cause double-resolution issues. Removepostcss-importunless you have non-Tailwind CSS files that need it.
9.4 Build Tool Integration Matrix
| Feature | CLI | PostCSS | Vite | Webpack | Standalone | Browser |
|---|---|---|---|---|---|---|
| Full compilation | Yes | Yes | Yes | Yes | Yes | Yes |
| Incremental rebuild | Via watch | Yes | Yes | Yes | Via watch | Yes |
| HMR | No | Via PostCSS | Yes (native) | Via loader | No | Yes (DOM) |
| Source maps | Yes | Yes | Yes | Yes | Yes | No |
| Minification | --minify |
optimize.minify |
Auto (prod) | optimize.minify |
--minify |
No |
| Plugin support | Yes | Yes | Yes | Yes | Yes (bundled) | No |
| CSS Modules | No | No | Via Vite | Yes | No | No |
@source |
Yes | Yes | Yes | Yes | Yes | No |
@config |
Yes | Yes | Yes | Yes | Yes (bundled) | No |
| Watch mode | --watch |
Via PostCSS | Dev server | Dev server | --watch |
MutationObserver |
9.5 Testing Strategy
The project uses vitest for unit and integration tests and Playwright for browser E2E tests.
Test organization:
packages/tailwindcss/
__tests__/ Core compiler tests
integrations/
cli/ CLI integration tests
postcss/ PostCSS plugin tests
vite/ Vite plugin tests
webpack/ Webpack loader tests
upgrade/ Migration tool tests
Running tests:
# Unit tests (vitest)
pnpm test
# Specific package
pnpm --filter tailwindcss test
# Integration tests
pnpm --filter @tailwindcss/integrations test
# Rust tests
cargo test --workspace
Test patterns:
- Compiler tests assert CSS output for given input CSS + candidate lists.
- Integration tests spin up real build tool processes and verify output files.
- Oxide tests verify candidate extraction against known inputs.
- Upgrade tests verify codemod transformations against before/after fixtures.
9.6 Build System
TypeScript packages are built with tsup (esbuild-based bundler):
pnpm build # Build all packages via turbo
pnpm --filter tailwindcss build # Build specific package
Rust crates are built with Cargo:
cargo build --release --workspace # Build all crates
cargo build -p oxide --release # Build oxide only
N-API binaries are built via napi-rs:
cd packages/@tailwindcss-oxide
pnpm build # Builds native binary for current platform
Turborepo manages the build graph:
turbo.jsondefines task dependencies (e.g.,builddepends on upstreambuild).- Turbo caches build outputs — rebuilds only what changed.
- Remote caching can be enabled for CI.
Standalone binary:
cd packages/@tailwindcss-standalone
bun build ./src/index.ts --compile --target=bun-linux-x64 --outfile=tailwindcss
9.7 Contributing Workflow
# Clone and setup
git clone https://github.com/tailwindlabs/tailwindcss.git
cd tailwindcss
pnpm install
cargo build --workspace
# Development
pnpm dev # Watch mode for all packages
pnpm test # Run tests
pnpm format # Prettier formatting
# Before submitting
pnpm build # Full build
pnpm test # Full test suite
cargo test # Rust tests
cargo clippy # Rust linting
9.8 Known Issues
| Issue | Description | Workaround |
|---|---|---|
| #19863 | Oxide scanner fails with certain arbitrary values | Use CSS custom properties instead of complex arbitrary values |
| #19930 | SOURCEMAP_BROKEN warnings in Nuxt/Vite setups | Source map chain breaks in multi-plugin PostCSS setups. Reduce PostCSS plugin chain. |
| #17958 | Missing lightningcss native module | Reinstall with npm rebuild or use a supported platform |
| #18540 | CLI watch mode fails with Docker bind mounts | Use volume mounts or polling-based watcher |
| #19893 | module.register() deprecation warning |
Node.js loader API change. Cosmetic warning, no functional impact. |
| #15806 | Missing oxide native module on Linux ARM | Ensure @tailwindcss/oxide-linux-arm64-gnu (or musl variant) is installed. Use standalone binary as fallback. |
9.9 Version Compatibility
| Tailwind CSS | Node.js | PostCSS | Vite | Webpack |
|---|---|---|---|---|
| 4.0.x | 18+ | 8.x | 5.x, 6.x | 5.x |
| 4.1.x | 18+ | 8.x | 5.x, 6.x | 5.x |
| 4.2.x | 18+ | 8.x | 5.x, 6.x, 7.x | 5.x |
Note
Vite 7 support was added in v4.2.x via the Vite Environment API. The plugin detects the Vite version at runtime and uses the appropriate API surface.
9.10 Upgrade Path: v3 to v4
The architectural changes from v3 to v4 are substantial:
| Aspect | v3 | v4 |
|---|---|---|
| Configuration | tailwind.config.js |
CSS @theme blocks |
| Plugin system | JS config plugins: [] |
CSS @plugin directive |
| Content scanning | content: ['./src/**/*.html'] |
Automatic (oxide + @source) |
| CSS processing | PostCSS-only | Standalone compiler + integrations |
| Class extraction | JavaScript regex | Rust state machines |
| Theme access | theme() function |
CSS custom properties (var(--color-*)) |
| Dark mode | darkMode: 'class' |
@variant dark (...) |
| Prefixes | prefix: 'tw-' |
CSS-level prefixing |
| Important | important: true |
CSS-level @layer ordering |
The @tailwindcss/upgrade tool automates most of this migration. Run it in the project root and review the changes:
npx @tailwindcss/upgrade
git diff # Review all changes before committing
Tip
The upgrade tool is conservative — it preserves functionality over style. After running it, manually review the generated
@themeblock for opportunities to simplify (e.g., removing values that match the default theme).