tailwindlabs/tailwindcss
Architecture
Tailwind CSS v4 has two cooperating engines:
- A CSS compiler written in TypeScript (
packages/tailwindcss). It parses the input CSS, builds a design system from@themeand plugins, and turns a list of class names into a stylesheet. - A content scanner written in Rust (
crates/oxide). It walks the project, reads source files, and extracts every string that might be a Tailwind class.
A thin NAPI binding (crates/node) exposes the scanner to JavaScript as the @tailwindcss/oxide package, and a set of bundler adapters (@tailwindcss/cli, /vite, /postcss, /webpack, /browser) wire the two halves together for a particular host environment.
The build, end to end
graph LR
Source[Source files<br/>HTML/JS/MDX/...]
Input[Input CSS<br/>@import 'tailwindcss']
Adapter["Adapter<br/>(Vite/PostCSS/Webpack/CLI)"]
Scanner["@tailwindcss/oxide<br/>Scanner"]
Compiler["tailwindcss.compile()"]
Build["compiler.build(candidates)"]
Optimize["@tailwindcss/node<br/>optimize() → lightningcss"]
Output[Output CSS]
Input --> Adapter
Adapter -->|input CSS| Compiler
Compiler -->|design system + sources| Build
Source --> Scanner
Adapter -->|sources| Scanner
Scanner -->|candidates| Build
Build -->|raw CSS| Optimize
Optimize --> OutputThe contract between adapter and compiler is two functions exported from packages/tailwindcss/src/index.ts:
compile(css, options)parses the input CSS, registers everything (@theme,@utility,@variant,@plugin,@config,@source, etc.), and returns an object exposingbuild(candidates),buildSourceMap(),features,root, andsources.compileAst(ast, options)is the same pipeline but operates on an already-parsed AST. It is used by@tailwindcss/postcssso the PostCSS AST can be shared.
Adapters call compile once, ask Scanner for candidates whenever files change, then call compiler.build([...candidates]) to produce CSS.
The compiler pipeline
graph TD
A[Input CSS string] -->|css-parser.ts| B[CSS AST]
B -->|substituteAtImports| C[After @import resolution]
C -->|walk: collect at-rules| D[Theme + variants + utilities + plugins + sources]
D -->|applyCompatibilityHooks| E[v3 plugins/config bridged into design system]
E -->|buildDesignSystem| F[DesignSystem]
F -->|substituteAtApply, substituteAtVariant, substituteFunctions| G[Static AST<br/>without @tailwind utilities]
G -->|build candidates → compileCandidates| H[Per-candidate rules]
H -->|sort by variant + property order| I[Final AST]
I -->|toCss + optimizeAst| J[Output CSS string]- CSS parsing lives in
packages/tailwindcss/src/css-parser.ts. It produces the AST node types defined inpackages/tailwindcss/src/ast.ts(StyleRule,AtRule,Declaration,Comment,Context,AtRoot). @importresolution inpackages/tailwindcss/src/at-import.tscalls back to aloadStylesheetfunction the adapter provides so imports can resolve through Vite/Webpack/enhanced-resolve.- The first walk through the AST (
packages/tailwindcss/src/index.ts,parseCss) collects@theme,@layer,@utility,@custom-variant,@variant,@plugin,@config,@source,@apply,theme(...), and--spacing(…)usages. It also picks up the legacy v3important,prefix,darkMode, and screen behavior through@theme. - Plugins and JS configs loaded via
@pluginor@configare bridged throughpackages/tailwindcss/src/compat/(seeapply-compat-hooks.ts,plugin-api.ts,apply-config-to-theme.ts). The compat layer translates v3addUtilities/matchUtilities/addVariantcalls into v4 utility/variant registrations. - Candidate compilation is in
packages/tailwindcss/src/compile.tsandpackages/tailwindcss/src/utilities.ts.parseCandidate(incandidate.ts) parses a string likemd:hover:bg-red-500/50into a typedCandidate.compileCandidatesthen asks each utility'scompileFnto emit AST nodes, applies any variants, and sorts by global property order (packages/tailwindcss/src/property-order.ts). optimizeAstinpackages/tailwindcss/src/ast.tsdeduplicates rules, merges adjacent declarations, and applies the@propertyandcolor-mixpolyfills.
The scanner pipeline
graph TD
A[Sources: globs + base dirs] --> B[Walker<br/>ignore + globwalk]
B --> C[Per-file fast-skip check<br/>fast_skip.rs]
C -->|kept| D[Pre-processor<br/>per extension<br/>extractor/pre_processors/]
D --> E[Extractor state machines<br/>candidate_machine.rs et al.]
E --> F[Candidate strings]
F --> G[Boundary trimming<br/>boundary.rs]
G --> H[Set of unique candidates]- The walker comes from the vendored
crates/ignorecrate; it respects.gitignore,.tailwindcssignore, hidden files, and binary extensions defined incrates/oxide/src/scanner/auto_source_detection.rs. - The extractor is implemented as a stack of small state machines (
crates/oxide/src/extractor/*_machine.rs) sharing aCursorover the byte slice. Each machine recognizes one shape: candidates, named utilities, named variants, modifiers, arbitrary values, arbitrary properties, arbitrary variables, CSS variables, strings, and brackets. - Pre-processors normalize per-language quirks before extraction. They live in
crates/oxide/src/extractor/pre_processors/(Haml, Pug, Razor, Ruby, Slim, Svelte, Vue, etc.). Scanner::scanandScanner::scan_contentare exposed viacrates/node/src/lib.rsas theScannerclass in@tailwindcss/oxide.
How adapters compose the two halves
Every adapter follows the same shape:
- Build (or fetch a cached)
compile(...)result for the input CSS. - Construct (or update) a
Scannerfromcompiler.sources,compiler.root, and the host's notion of the project base directory. - On each rebuild: scan changed content, accumulate candidates, call
compiler.build([...candidates]), optionally pass throughoptimize(...)from@tailwindcss/node.
The differences are mostly about plumbing: @tailwindcss/vite (packages/@tailwindcss-vite/src/index.ts) registers itself as multiple Vite plugins covering pre/post environments, transforms, watch invalidations, and HMR. @tailwindcss/postcss (packages/@tailwindcss-postcss/src/index.ts) caches per-input file in a QuickLRU and converts between PostCSS and the internal AST through cssAstToPostCssAst / postCssAstToCssAst (packages/@tailwindcss-postcss/src/ast.ts). @tailwindcss/webpack (packages/@tailwindcss-webpack/src/index.ts) adds dependency tracking via webpack's addDependency/addContextDependency. @tailwindcss/cli (packages/@tailwindcss-cli/src/commands/build/index.ts) drives the loop manually using @parcel/watcher.
The browser build (packages/@tailwindcss-browser/src/index.ts) replaces the Rust scanner entirely with a MutationObserver that walks document.querySelectorAll('[class]') and feeds class lists straight into compiler.build(...).
Performance design notes
- Candidates are strings, not parsed objects. The scanner outputs raw strings; the compiler is responsible for parsing them via
parseCandidate. ADesignSystemkeeps aninvalidCandidatesset so duplicate failed parses are short-circuited. - Caches are invalidated by file mtime, not content hashes. Each adapter keeps an
mtimesMapkeyed by file path.@tailwindcss/postcss,@tailwindcss/webpack, and the CLI watch file mtimes viafs.statSync; touching a file forces a "full rebuild" that re-runscompile(). Featuresflags drive bail-out.Features(inpackages/tailwindcss/src/index.ts) is a bit-set indicating which Tailwind features the input CSS actually uses. Adapters check it to skip work when a CSS file uses no Tailwind features.- The compiler returns a static AST that is reused.
compile()returns a functionbuild(candidates)that re-runs only the candidate compilation step on top of a cached parsed AST. This is why repeat builds in dev mode are fast. - Polyfills and minification run once at the end.
@propertyfallbacks andcolor-mix(...)fallbacks are emitted byoptimizeAst. Final minification goes through Lightning CSS inpackages/@tailwindcss-node/src/optimize.ts.
Cross-cutting concerns
- Source maps are produced by
packages/tailwindcss/src/source-maps/and remapped through the adapters viatoSourceMapinpackages/@tailwindcss-node/src/index.ts. See Source maps. - v3 compatibility is implemented in
packages/tailwindcss/src/compat/and bridges JavaScript configs and plugins. See v3 compatibility. - Variants —
hover:,md:,dark:, group/peer, has-, not-, etc. — are registered inpackages/tailwindcss/src/variants.tsand applied during candidate compilation. See Variants. - The "design system" is the runtime context bundling the theme, registered utilities, variants, prefix, and important state. See Design system.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.