# Data flow — tailwindlabs/tailwindcss

> Canonical page: https://codevetter.com/unpack/tailwindcss/data-flow

# Data flow — tailwindlabs/tailwindcss

Scanned commit 41d9cae8e53378d16087fcf359eb785c2fd42ce4 (2026-09-08). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

There are two independent input streams that converge at `build()`. Stream one is CSS: a stylesheet string is parsed by `CSS.parse`, `@import`s are inlined, at-rules are walked to populate a `Theme`, a `Utilities` registry, and a `Variants` registry, and the result is a `DesignSystem`. Stream two is candidates: the Rust `Scanner` walks the filesystem (gitignore-aware), pre-processes files by extension, runs byte-level state machines to extract class-name-shaped strings, and returns a sorted `Vec<String>` across the NAPI boundary. `build(candidates)` compiles those strings against the design system into AST nodes, splices them into the `@tailwind utilities` placeholder node, runs `optimizeAst`, and prints CSS via `toCss`. State ownership is explicit: `Theme` owns variables, `DesignSystem` owns caches, and the Rust `Scanner` owns file/mtime/candidate sets.
- Input boundary one: `compile(css, opts)` calls `CSS.parse(css, { from })` then `parseCss`, which first wraps the AST in `contextNode({ base }, ast)` and resolves `@import`s through `substituteAtImports` before any at-rule walking happens. — [packages/tailwindcss/src/index.ts#L336-L346](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L336-L346) [packages/tailwindcss/src/index.ts#L820-L830](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L820-L830) [packages/tailwindcss/src/at-import.ts#L16-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/at-import.ts#L16-L60)
- `@theme` declarations are drained into a `Theme` map keyed by unescaped custom-property name, carrying `ThemeOptions` bit flags (`INLINE`, `REFERENCE`, `DEFAULT`, `STATIC`, `USED`) and a source location; `@keyframes` inside `@theme` are pulled out into `theme.addKeyframes` and re-hoisted as `atRoot` nodes. — [packages/tailwindcss/src/index.ts#L560-L620](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L560-L620) [packages/tailwindcss/src/theme.ts#L1-L85](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/theme.ts#L1-L85)
- The first `@theme` rule is replaced by a `:root, :host` style rule whose body is filled in *after* the compat hooks run, so JS-config theme extensions land in the same emitted variable block. — [packages/tailwindcss/src/index.ts#L596-L610](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L596-L610) [packages/tailwindcss/src/index.ts#L700-L730](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L700-L730)
- `@source` at-rules are split at parse time: `inline(...)` forms are brace-expanded into `inlineCandidates` (or `ignoredCandidates` when negated, which seed `designSystem.invalidCandidates`), while path forms become `{ base, pattern, negated }` entries returned as `compiler.sources`. — [packages/tailwindcss/src/index.ts#L258-L305](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L258-L305) [packages/tailwindcss/src/index.ts#L661-L672](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L661-L672)
- Every integration builds the scanner source list with the same three-branch rule: `root === 'none'` → no sources, `root === null` → `{ base, pattern: '**/*' }`, else the explicit `source(…)` root, concatenated with `compiler.sources`. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L285-L305](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L285-L305) [packages/@tailwindcss-vite/src/index.ts#L410-L428](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L410-L428) [packages/@tailwindcss-postcss/src/index.ts#L207-L226](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L207-L226)
- Input boundary two: `Scanner::scan()` calls `discover_sources()` (a `WalkBuilder` walk that populates `files`, `dirs`, `extensions`, and `mtimes`), then `extract_candidates()`, and returns a `par_sort_unstable`-sorted candidate vector. — [crates/oxide/src/scanner/mod.rs#L119-L135](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L119-L135) [crates/oxide/src/scanner/mod.rs#L367-L470](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L367-L470)
- File bytes are transformed before extraction: `pre_process_input(content, extension)` rewrites dialect-specific syntax (e.g. Pug/Haml/Slim shorthand) into something the generic extractor can read, then `Extractor::extract` runs a `CssVariableMachine` and a `CandidateMachine` over the bytes, emitting `Extracted::Candidate` and `Extracted::CssVariable`. — [crates/oxide/src/scanner/mod.rs#L541-L562](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L541-L562) [crates/oxide/src/extractor/mod.rs#L55-L90](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/extractor/mod.rs#L55-L90)
- CSS files found during the walk are routed separately: `extract_candidates` calls `extract_css_variables` on them so that `--my-var` references in stylesheets count as used variables, distinct from utility candidates. — [crates/oxide/src/scanner/mod.rs#L233-L250](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L233-L250) [crates/oxide/src/scanner/mod.rs#L578-L584](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L578-L584)
- Candidate strings beginning with `--` are not compiled as utilities at all — `build()` routes them to `designSystem.theme.markUsedVariable(candidate)`, which is how theme variables referenced only from markup survive tree-shaking. — [packages/tailwindcss/src/index.ts#L760-L775](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L760-L775) [packages/tailwindcss/src/theme.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/theme.ts)
- `compileCandidates` parses each raw candidate into `Candidate[]`, compiles each to AST nodes via `designSystem.compileAstNodes`, and sorts the result by a BigInt variant bitmask first, then by `GLOBAL_PROPERTY_ORDER` index, then by property count, then alphabetically — this is what produces Tailwind's stable output ordering. — [packages/tailwindcss/src/compile.ts#L12-L112](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compile.ts#L12-L112) [packages/tailwindcss/src/property-order.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/property-order.ts)
- Output boundary: `build()` mutates `utilitiesNode.nodes` in place, runs `optimizeAst(ast, designSystem, opts.polyfills)`, and `compile()`'s wrapper only re-runs `toCss` when the returned AST object identity changed — an explicit identity-based memoization. — [packages/tailwindcss/src/index.ts#L790-L815](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L790-L815) [packages/tailwindcss/src/index.ts#L832-L848](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L832-L848) [packages/tailwindcss/src/ast.ts#L225-L240](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/ast.ts#L225-L240)
- Polyfills are a caller-controlled bitfield (`Polyfills.AtProperty`, `Polyfills.ColorMix`, `Polyfills.All`); the PostCSS plugin masks out `AtProperty` for `.module.css` files because the emitted global `*` rules break CSS Modules purity checks. — [packages/tailwindcss/src/index.ts#L239-L252](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L239-L252) [packages/@tailwindcss-postcss/src/index.ts#L138-L146](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L138-L146)
- The PostCSS integration converts between AST dialects on both ends — `postCssAstToCssAst(root)` on the way in and `cssAstToPostCssAst(postcss, tailwindCssAst, root.source)` on the way out — and caches the converted PostCSS root so unchanged builds clone rather than re-convert. — [packages/@tailwindcss-postcss/src/index.ts#L128-L136](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L128-L136) [packages/@tailwindcss-postcss/src/index.ts#L279-L310](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L279-L310) [packages/@tailwindcss-postcss/src/ast.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/ast.ts)
- Asset URL rewriting is an opt-in transform in the IO layer, not the core: `@tailwindcss/node`'s `loadStylesheet` wrapper calls `rewriteUrls({ css, root, base })` only when `shouldRewriteUrls` is set, which Vite always sets and PostCSS gates behind `transformAssetUrls`. — [packages/@tailwindcss-node/src/compile.ts#L47-L62](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L47-L62) [packages/@tailwindcss-node/src/urls.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/urls.ts) [packages/@tailwindcss-vite/src/index.ts#L392-L400](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L392-L400)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
