# tailwindlabs/tailwindcss — codebase unpack

> Canonical page: https://codevetter.com/unpack/tailwindcss

This is a deterministic Repo Unpack scan of tailwindlabs/tailwindcss — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 41d9cae8e53378d16087fcf359eb785c2fd42ce4 (2026-09-08)
- Last upstream commit: Reject candidates with multiple modifiers (#20466)
- Generated by codevetter 1.12.0 on 2026-09-22
- 547 files scanned · 5.3 MB

## Languages

- TypeScript: 307 files
- Rust: 61 files
- JSON: 47 files
- Markdown: 29 files
- CSS: 18 files
- JavaScript: 16 files
- TOML: 9 files
- YAML: 7 files

## Repository structure

- packages/ — 323 files
- crates/ — 116 files
- integrations/ — 45 files
- playgrounds/ — 31 files
- .github/ — 12 files
- scripts/ — 6 files
- patches/ — 3 files

Probable source entrypoint: `Cargo.toml` (inferred from the tree, not upstream docs)

## Source layout — packages/

- packages/@tailwindcss-browser/ — 11 files
- packages/@tailwindcss-cli/ — 18 files
- packages/@tailwindcss-node/ — 20 files
- packages/@tailwindcss-postcss/ — 23 files
- packages/@tailwindcss-standalone/ — 5 files
- packages/@tailwindcss-turbopack/ — 6 files
- packages/@tailwindcss-webpack/ — 6 files
- packages/tailwindcss/ — 11 files
- packages/tsconfig.base.json — 1 file

## Package

- Cargo.toml: null — 0 declared dependencies

## Structural health signals

Average score 7.1/10 across 96 files; 28 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 41d9cae8e533 (2026-09-08) Reject candidates with multiple modifiers (#20466)
- f723e834ad03 (2026-08-31) Don't warn about Angular's `::ng-deep` and `:host-context()` when optimizing CSS (#20434)
- 9f451eead86b (2026-08-31) Cold start performance improvements (#20427)
- 90f8ff41c8e2 (2026-08-15) Don't space out `and`/`or`/`not` inside function calls in `supports-[…]` variants (#20420)
- 7a7f38664821 (2026-08-14) Don't emit utilities that resolve a theme value when an unsupported modifier is used (#20419)

## Overview

This is the Tailwind CSS v4 monorepo: a CSS-first utility framework whose compiler is a TypeScript AST pipeline (`packages/tailwindcss`) paired with a Rust candidate scanner compiled to a NAPI/WASM native addon (`crates/oxide` → `@tailwindcss/oxide`). Configuration moved out of `tailwind.config.js` and into CSS at-rules — `@theme`, `@utility`, `@custom-variant`, `@source`, `@plugin`, `@config`, `@apply` — with a dedicated `compat/` layer that replays legacy v3 JS configs and plugins onto the new design system. It ships as a fan of thin integration packages (CLI, PostCSS, Vite, webpack, Turbopack, browser CDN build, Bun-compiled standalone binary) that all wrap the same `compile()` + `Scanner` pair, plus an AST-driven `@tailwindcss/upgrade` codemod tool for v3→v4 migration. It targets web developers and build-tool authors; what's distinctive is the split-language architecture and the aggressive incremental-rebuild caching that appears in every integration.

## System map

The repo is a pnpm workspace (`pnpm-workspace.yaml`) orchestrated by Turborepo (`turbo.json`) layered over a Cargo workspace (`Cargo.toml`, `members = ["crates/*"]`). The compiler core lives in `packages/tailwindcss` and is pure TypeScript with no filesystem access — all IO is injected via `loadModule`/`loadStylesheet` callbacks. `@tailwindcss/node` is the Node IO adapter (enhanced-resolve + jiti + Lightning CSS), and `@tailwindcss/oxide` is the Rust scanner bridged through napi-rs. Six consumer packages (cli, postcss, vite, webpack, turbopack, browser, standalone) sit on top, and `@tailwindcss/upgrade` is a separate codemod CLI. There is no server or database; the storage layer is the filesystem plus in-memory LRU caches.
- `packages/tailwindcss/src/index.ts` exports the three public compiler entrypoints — `compile(css, opts)` (string→string), `compileAst(ast, opts)` (AST→AST, used by the PostCSS plugin), and `__unstable__loadDesignSystem` (used by IntelliSense and the upgrade tool) — plus a default export that throws a hard error if someone wires `tailwindcss` directly into PostCSS. — [packages/tailwindcss/src/index.ts#L620-L871](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L620-L871)
- The core compiler takes zero filesystem dependencies: `CompileOptions` declares `loadModule` and `loadStylesheet` callbacks that default to `throwOnLoadModule`/`throwOnLoadStylesheet`, which is what makes the same code run in Node, Bun, and the browser CDN build. — [packages/tailwindcss/src/index.ts#L253-L281](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L253-L281) [packages/@tailwindcss-browser/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-browser/src/index.ts)
- `@tailwindcss/node` is the Node runtime boundary: it wires `enhanced-resolve` resolvers (separate `cssResolver` with `mainFields: ['style']`, `esmResolver`, and `cjsResolver` fallback), `jiti` for `.ts` plugin/config files, and `Module.registerHooks({ resolve: resolveSync })` so ESM config reloads work in watch mode. — [packages/@tailwindcss-node/src/compile.ts#L170-L260](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L170-L260) [packages/@tailwindcss-node/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/index.ts)
- `crates/node/src/lib.rs` is the napi-rs bridge exposing the `Scanner` class (`scan()`, `scanFiles()`, `getCandidatesWithPositions()`, and `files`/`scannedFiles`/`globs`/`normalizedSources` getters) plus the `ChangedContent`, `GlobEntry`, and `SourceEntry` object shapes consumed by every JS integration. — [crates/node/src/lib.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/node/src/lib.rs) [crates/oxide/src/scanner/mod.rs#L1-L130](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L1-L130)
- The Cargo workspace has four crates: `tailwindcss-oxide` (the scanner/extractor), `tailwind-oxide` (`crates/node`, the NAPI shim), `classification-macros` (a `ClassifyBytes` proc-macro deriving byte-class lookup tables for the extractor state machines), and a vendored fork of BurntSushi's `ignore` crate pinned to edition 2024. — [Cargo.toml](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/Cargo.toml) [crates/classification-macros/src/lib.rs#L1-L40](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/classification-macros/src/lib.rs#L1-L40) [crates/ignore/Cargo.toml#L1-L20](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/ignore/Cargo.toml#L1-L20) [crates/oxide/Cargo.toml](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/Cargo.toml)
- Build is `pnpm build` → `turbo build --filter=!./playgrounds/*`, followed by a `postbuild` hook that runs `scripts/pack-packages.mjs` to `pnpm pack` every non-private workspace into `dist/*.tgz` — the integration tests then install those tarballs. — [package.json#L28-L33](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/package.json#L28-L33) [scripts/pack-packages.mjs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/scripts/pack-packages.mjs) [integrations/utils.ts#L15-L22](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/utils.ts#L15-L22)
- Test commands are split three ways: `pnpm test` runs `cargo test && vitest run`, `pnpm test:integrations` runs `vitest --root=./integrations`, and `pnpm test:ui` runs Playwright in both `tailwindcss` and `@tailwindcss/browser`. — [package.json#L34-L40](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/package.json#L34-L40) [vitest.config.mts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/vitest.config.mts) [integrations/vitest.config.mts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/vitest.config.mts)
- `turbo.json` gives `@tailwindcss/oxide#build` explicit `inputs` covering `../oxide/src/**/*` and `../Cargo.toml`, so touching Rust source correctly busts the Turbo cache for the native addon; it also passes `RUSTUP_HOME` through via `globalPassThroughEnv`. — [turbo.json](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/turbo.json)
- Deployment shape is npm publishing with provenance: `@tailwindcss/oxide` fans out into 12 platform-specific optionalDependencies (`napi.targets` includes `wasm32-wasip1-threads`), and `.github/workflows/release.yml` builds each target in a matrix then publishes with `id-token: write` for npm trusted publishing. — [crates/node/package.json](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/node/package.json) [.github/workflows/release.yml#L30-L80](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/release.yml#L30-L80) [.github/workflows/release.yml#L205-L235](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/release.yml#L205-L235)
- `@tailwindcss/standalone` is built by Bun (`bun ./scripts/build.ts`) into seven single-file executables (linux/macos/windows × arm64/x64, glibc and musl), each SHA-256 checksummed; it is marked `private: true` so `pack-packages.mjs` skips it. — [packages/@tailwindcss-standalone/package.json](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-standalone/package.json) [packages/@tailwindcss-standalone/scripts/build.ts#L23-L100](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-standalone/scripts/build.ts#L23-L100)
- Lightning CSS is the only output post-processor: `optimize()` in `@tailwindcss/node` runs `transform()` twice (so nesting-flattened adjacent rules get merged), pins browser targets to Safari/iOS 16.4, Firefox 128, Chrome 111, and uses `errorRecovery: true` with a warning filter for framework pseudo-classes like `::ng-deep` and `::v-deep`. — [packages/@tailwindcss-node/src/optimize.ts#L28-L90](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/optimize.ts#L28-L90)
- pnpm patches three dependencies — `@napi-rs/cli`, `@parcel/watcher`, and `lightningcss` — specifically so Bun can statically analyze them when compiling the standalone binary. — [pnpm-workspace.yaml#L8-L12](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/pnpm-workspace.yaml#L8-L12) [packages/@tailwindcss-standalone/package.json](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-standalone/package.json)
- `playgrounds/` holds three manual smoke-test apps (vite + React, nextjs, v3) wired via root scripts `pnpm vite` and `pnpm nextjs`; the Vite one is a two-line config registering `@tailwindcss/vite` alongside `@vitejs/plugin-react`. — [playgrounds/vite/vite.config.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/playgrounds/vite/vite.config.ts) [package.json#L41-L43](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/package.json#L41-L43) [.github/CONTRIBUTING.md#L30-L37](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L30-L37)

## Feature catalog

User-facing surface is (a) the CSS authoring language — a set of at-rules parsed in `parseCss`, (b) the `tailwindcss` CLI with `build` and `canonicalize` subcommands, (c) five build-tool integrations, (d) a zero-build browser CDN script, and (e) the `@tailwindcss/upgrade` migration CLI. The compat layer additionally re-exposes the entire v3 JS plugin API (`addUtilities`, `matchVariant`, `theme()`, etc.) so existing plugins keep working. IntelliSense-facing APIs (`getClassList`, `getVariants`, `canonicalizeCandidates`) are first-class exports of the design system rather than an afterthought.
- The CSS at-rule vocabulary handled in `parseCss` is `@tailwind utilities`, `@utility`, `@source` (with `not`, `inline(…)`, and brace-expansion via `expand()`), `@variant`, `@custom-variant`, `@theme` (with `reference`/`inline`/`default`/`static`/`prefix(…)` options), `@apply`, `@import`, `@plugin`, and `@config`. — [packages/tailwindcss/src/index.ts#L336-L560](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L336-L560) [packages/tailwindcss/src/compat/apply-compat-hooks.ts#L54-L90](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/apply-compat-hooks.ts#L54-L90)
- `@import "tailwindcss" theme(reference)`, `prefix(ident)`, `important`, and `source(…)` are implemented by rewriting the `@media` node that `postcss-import`-style resolution produces — `@media theme(reference)` is transformed into a `context({ reference: true })` wrapper rather than being a distinct parser path. — [packages/tailwindcss/src/index.ts#L455-L540](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L455-L540)
- The `tailwindcss` CLI exposes `build` (default when no subcommand is given) and `canonicalize`, with root-level `--help` dispatch in `run()` that also errors on unknown non-flag commands. — [packages/@tailwindcss-cli/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/index.ts)
- `tailwindcss build` supports `--input/-i` (including `-` for stdin), `--output/-o`, `--watch[=always]`, `--poll[=ms]`, `--minify/-m`, `--optimize`, `--cwd`, `--map`, and `--silent`; when no `--input` is given it defaults to compiling `@import 'tailwindcss';`. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L29-L78](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L29-L78) [packages/@tailwindcss-cli/src/commands/build/index.ts#L178-L188](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L178-L188)
- `tailwindcss canonicalize` rewrites candidate lists to their canonical form, reading from argv or stdin, with `--css` to point at a design-system entry, `--format text|json|jsonl`, and `--stream` for line-by-line stdin processing — aimed at tooling like Prettier plugins and linters. — [packages/@tailwindcss-cli/src/commands/canonicalize/index.ts#L36-L120](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/canonicalize/index.ts#L36-L120) [packages/tailwindcss/src/canonicalize-candidates.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/canonicalize-candidates.ts)
- `@tailwindcss/vite` ships as an array of three Vite plugins — `@tailwindcss/vite:scan` (config capture), `:generate:serve` (dev), and `:generate:build` (production, which additionally runs `optimize()`) — each with `transform.filter` id include/exclude regexes that skip `?raw`, `?url`, `?worker`, and CommonJS proxy requests. — [packages/@tailwindcss-vite/src/index.ts#L166-L285](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L166-L285)
- `@tailwindcss/postcss` accepts `base`, `optimize` (defaults to `NODE_ENV === 'production'`), and `transformAssetUrls`, and bundles a `fixRelativePathsPlugin()` that repairs paths when `postcss-import` ran before it. — [packages/@tailwindcss-postcss/src/index.ts#L52-L84](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L52-L84) [packages/@tailwindcss-postcss/src/postcss-fix-relative-paths](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/postcss-fix-relative-paths)
- `@tailwindcss/webpack` and `@tailwindcss/turbopack` are near-identical webpack `LoaderContext` loaders taking `{ base, optimize }`, both keyed into a shared `QuickLRU` cache of max size 50 via `getCacheKey(resourceId, opts)`. — [packages/@tailwindcss-webpack/src/index.ts#L18-L80](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-webpack/src/index.ts#L18-L80) [packages/@tailwindcss-turbopack/src/index.ts#L18-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-turbopack/src/index.ts#L18-L60)
- `@tailwindcss/browser` is a CDN build that reads `<style type="text/tailwindcss">` blocks, auto-injects `@import "tailwindcss";` when the user's CSS has no `@import`, and serves `tailwindcss`, `/preflight`, `/theme`, and `/utilities` from inlined `assets.css.*` strings; plugins and config files throw. — [packages/@tailwindcss-browser/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-browser/src/index.ts)
- The v3 JS plugin API is fully re-implemented in `compat/plugin-api.ts`: `addBase`, `addVariant`, `matchVariant`, `addUtilities`, `matchUtilities`, `addComponents`/`matchComponents` (which just delegate to the utility variants), and a `theme()` function built by `createThemeFn`. — [packages/tailwindcss/src/compat/plugin-api.ts#L39-L80](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/plugin-api.ts#L39-L80) [packages/tailwindcss/src/compat/plugin-api.ts#L266-L560](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/plugin-api.ts#L266-L560)
- `tailwindcss` re-exports v3 compatibility entry points as separate package exports: `tailwindcss/colors`, `tailwindcss/defaultTheme`, `tailwindcss/plugin`, and `tailwindcss/lib/util/flattenColorPalette`, each with paired `.ts`/`.cts` implementations for ESM and CJS. — [packages/tailwindcss/package.json#L18-L70](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/package.json#L18-L70) [packages/tailwindcss/src/compat/colors.cts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/colors.cts) [packages/tailwindcss/src/compat/default-theme.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/default-theme.ts)
- `@tailwindcss/upgrade` is a standalone migration CLI (`--config`, `--force`, `--version`) that refuses to run on a dirty git tree, migrates stylesheets, migrates `tailwind.config.js` into CSS, rewrites class names in templates, and then shells out to the detected package manager to bump `tailwindcss` and friends to `@latest`. — [packages/@tailwindcss-upgrade/src/index.ts#L28-L100](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/index.ts#L28-L100) [packages/@tailwindcss-upgrade/src/index.ts#L263-L290](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/index.ts#L263-L290)
- The Oxide scanner ships language-aware pre-processors for 14 template dialects, dispatched by file extension: Clojure, Elixir/HEEx, Razor, Haml, JSON, Markdown, Pug, Ruby/ERB, Slim, Svelte, Rust, Template Toolkit, Twig, and Vue. — [crates/oxide/src/scanner/mod.rs#L541-L560](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L541-L560) [crates/oxide/src/extractor/pre_processors/mod.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/extractor/pre_processors/mod.rs)
- Source maps are a supported output across surfaces: the core exposes `buildSourceMap()` returning a `DecodedSourceMap`, the CLI writes it inline or to a file via `--map`, and Vite emits it when `config.css.devSourcemap` is on. — [packages/tailwindcss/src/index.ts#L855-L862](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L855-L862) [packages/@tailwindcss-cli/src/commands/build/index.ts#L226-L255](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L226-L255) [packages/@tailwindcss-vite/src/index.ts#L495-L505](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L495-L505)

## Data flow

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)

## Behavior traces

Three flows matter most: a cold CLI build, a CLI watch-mode rebuild, and the Vite dev-server transform. All three share the same skeleton — create compiler, derive sources, construct `Scanner`, scan, build, write — but differ in how they decide between a full and an incremental rebuild. The browser CDN build is a fourth, MutationObserver-driven variant of the same loop. The distinguishing detail across all of them is that 'full rebuild' means *discarding and recreating the compiler*, while 'incremental' means only feeding new candidate strings into the existing one.
- Cold CLI build order: `run()` → `build.handle(flags)` → resolve `--cwd`/`--input`/`--output` to absolute paths and reject identical in/out → read input (or `drainStdin()`, or default `@import 'tailwindcss';`) → `createCompiler(input, I)` → `scanner.scan()` → `compiler.build(candidates)` → `write(output, map, args, I)` → print `Done in …`. — [packages/@tailwindcss-cli/src/index.ts#L30-L79](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/index.ts#L30-L79) [packages/@tailwindcss-cli/src/commands/build/index.ts#L118-L190](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L118-L190) [packages/@tailwindcss-cli/src/commands/build/index.ts#L494-L520](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L494-L520)
- `createCompiler` registers an `onDependency(path)` callback that pushes into `fullRebuildPaths`, and then deliberately appends a *negated* source for `path.basename(process.execPath)` so the standalone binary never scans itself — a rename-proof self-exclusion. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L277-L320](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L277-L320)
- Watch mode calls `loadWatcher()` first (so a missing `@parcel/watcher` fails with a useful message before any watcher is registered), then `createWatchers(await watchDirectories(scanner), handle)`. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L330-L340](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L330-L340) [packages/@tailwindcss-cli/src/commands/build/index.ts#L685-L700](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L685-L700)
- The watch handler short-circuits when the only changed file is `args['--output']`, preventing the write-triggers-rebuild infinite loop, then calls `getRebuildStrategy(files, fullRebuildPaths)` to choose a branch. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L341-L352](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L341-L352) [packages/@tailwindcss-cli/src/commands/build/index.ts#L800-L840](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L800-L840)
- On a `full` rebuild the CLI does, in order: re-read input → `clearRequireCache(resolvedFullRebuildPaths)` → snapshot `backupRebuildPaths` → reset `fullRebuildPaths` → `createCompiler` → `scanner.scan()` → set up new watchers → tear down old watchers → `compiler.build(candidates)`. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L355-L405](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L355-L405)
- On an `incremental` rebuild it calls `scanner.scanFiles(rebuildStrategy.changedFiles)` and returns early without writing if zero new candidates were found — so touching a file with no new classes costs no disk write. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L408-L430](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L408-L430)
- The watch `catch` block restores `fullRebuildPaths = backupRebuildPaths` before logging, with an inline comment explaining the recovery case: deleting a transitive dependency of `tailwind.config.js` would otherwise leave the compiler permanently unable to re-register that dependency. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L437-L475](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L437-L475)
- Polling mode is a separate code path from event-based watching: `--poll` is validated to be a positive number (default 250ms), and it drives a `fullRebuild(I)` closure plus cursor-hiding escape codes with `SIGINT`/`SIGTERM` handlers that exit 130/143. — [packages/@tailwindcss-cli/src/commands/build/index.ts#L166-L176](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L166-L176) [packages/@tailwindcss-cli/src/commands/build/index.ts#L522-L600](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L522-L600)
- Vite dev transform order: `isPotentialCssRootFile(id)` gate → look up a `Root` in `rootsByEnv` keyed by `this.environment?.name` → `root.generate(src, addWatchFile, I)` → return `{ code, map }`; returning `false` from `generate` deletes the root from the map so it can be garbage collected. — [packages/@tailwindcss-vite/src/index.ts#L194-L230](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L194-L230) [packages/@tailwindcss-vite/src/index.ts#L358-L380](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L358-L380)
- `Root.generate` decides to rebuild by calling `requiresBuild()`, which stats every entry in `buildDependencies` and returns true if any mtime advanced or any file is missing — an mtime-map strategy mirrored in the PostCSS and webpack plugins. — [packages/@tailwindcss-vite/src/index.ts#L505-L543](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L505-L543) [packages/@tailwindcss-postcss/src/index.ts#L160-L200](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L160-L200)
- After compiling, `Root.generate` bails with `false` unless the compiler reported at least one of `Features.AtApply | JsPluginCompat | ThemeFunction | Utilities | Variants` — this is how ordinary non-Tailwind `.css` files pass through Vite untouched. — [packages/@tailwindcss-vite/src/index.ts#L436-L450](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L436-L450) [packages/tailwindcss/src/index.ts#L313-L334](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L313-L334)
- The PostCSS plugin has an even cheaper bail: before touching the cache it does a `root.walkAtRules` scan for any of `import|reference|theme|variant|config|plugin|apply|tailwind` and returns immediately if none are present. — [packages/@tailwindcss-postcss/src/index.ts#L96-L117](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L96-L117)
- Browser CDN flow: `rebuild('full')` → `createCompiler()` gathers all `style[type="text/tailwindcss"]` text, compares against `lastCss` to skip needless recompiles → `build(kind)` collects `element.classList` across `[class]` elements, diffs against a global `classes` Set, and assigns `sheet.textContent = compiler.build(newClasses)`; builds are serialized through a `buildQueue` promise chain. — [packages/@tailwindcss-browser/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-browser/src/index.ts)
- Two MutationObservers drive the browser build: one watches each known `<style>` element for `characterData`/`childList` changes (→ full rebuild), the other watches `document.documentElement` for `class` attribute changes and added nodes (→ incremental), explicitly skipping the injected output `sheet` to avoid a feedback loop. — [packages/@tailwindcss-browser/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-browser/src/index.ts)
- Upgrade flow order: dirty-git check → version match check → glob `**/*.css` → `Stylesheet.load` → `analyzeStylesheets` → `linkConfigsToStylesheets` (v3 only) → `migrateJsConfig` → `migrateStylesheet` → `splitStylesheets` (v3 only) → `postcss([sortBuckets(), formatNodes()])` → `writeFileSafely` → package-manager upgrade → template migration. — [packages/@tailwindcss-upgrade/src/index.ts#L46-L300](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/index.ts#L46-L300)

## Testing signals

Testing is unusually thorough and layered: Vitest for ~80 co-located unit/snapshot suites, `cargo test` for the Rust scanner and the vendored `ignore` crate, a bespoke Vitest-based integration harness that materializes real temp projects and installs packed tarballs, and Playwright for browser-truth assertions about computed styles. CI runs unit and integration suites as two separate workflows, each with a three-OS matrix that is trimmed to Linux-only on feature branches unless the PR body contains `[ci-all]`. The gaps are mostly in the thin adapter layers — the CLI watch/poll machinery, the webpack/Turbopack loaders, and the release scripts have no direct unit tests.
- Vitest is configured at the root with `projects: ['./packages/*']` and excludes `*.spec.*` files and `integrations/**` — the `.spec.ts` extension is reserved for Playwright so the two runners never collide. — [vitest.config.mts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/vitest.config.mts) [packages/tailwindcss/vitest.config.mts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/vitest.config.mts)
- Roughly 80 `*.test.ts` files live beside their sources under `packages/`, dominated by `utilities.test.ts` at ~31,600 lines and `index.test.ts` at ~7,300 lines — the utility matrix is tested exhaustively by snapshot. — [packages/tailwindcss/src/utilities.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/utilities.test.ts) [packages/tailwindcss/src/index.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.test.ts) [packages/tailwindcss/src/__snapshots__](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/__snapshots__)
- `packages/tailwindcss/vitest.config.mts` registers a custom snapshot serializer at `src/test-utils/custom-serializer.ts`, which is why CSS snapshots read as formatted stylesheets rather than escaped strings. — [packages/tailwindcss/vitest.config.mts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/vitest.config.mts) [packages/tailwindcss/src/test-utils/custom-serializer.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/test-utils/custom-serializer.ts)
- Rust coverage centers on `crates/oxide/tests/scanner.rs` (~4,080 lines, 80+ `#[test]` cases inside a `mod scanner` block) covering source resolution, gitignore interaction, and symlink handling; the vendored `ignore` crate carries its own upstream tests. — [crates/oxide/tests/scanner.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/tests/scanner.rs) [crates/ignore/tests/gitignore_matched_path_or_any_parents_tests.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/ignore/tests/gitignore_matched_path_or_any_parents_tests.rs) [crates/ignore/tests/gitignore_skip_bom.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/ignore/tests/gitignore_skip_bom.rs)
- The integration harness in `integrations/utils.ts` exports a custom `test(name, config, cb)` that mkdtemps a real project directory, writes a `fs` map of files, and hands the callback `exec`, `spawn`, `fs.expectFileToContain`, and `parseSourceMap` helpers. — [integrations/utils.ts#L38-L120](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/utils.ts#L38-L120)
- Integration tests run against real packed tarballs: `PUBLIC_PACKAGES` is derived by reading `dist/` and mapping `tailwindcss-*.tgz` back to `@tailwindcss/*` names, which is why `pnpm build` must precede `pnpm test:integrations`. — [integrations/utils.ts#L15-L22](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/utils.ts#L15-L22) [.github/CONTRIBUTING.md#L70-L80](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L70-L80)
- There are 42 integration test files across six suites — `cli`, `oxide`, `postcss`, `turbopack`, `upgrade`, `vite`, `webpack` — with the Vite suite alone covering Astro, Nuxt, SvelteKit, SolidStart, Qwik, React Router, Preact, Vue, CSS Modules, SSR, and source maps. — [integrations/vite/astro.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/vite/astro.test.ts) [integrations/vite/sveltekit.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/vite/sveltekit.test.ts) [integrations/vite/css-modules.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/vite/css-modules.test.ts) [integrations/postcss/next.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/postcss/next.test.ts)
- The integration harness compensates for known CI flakiness: timeouts are 120s on Windows vs 60s elsewhere, `retry` defaults to 2 under CI, and on Windows CI the temp root is derived from `GITHUB_WORKSPACE` because `tmpdir()` returns a `RUNNER~1` short path that breaks Vite builds. — [integrations/utils.ts#L82-L100](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/utils.ts#L82-L100)
- Playwright covers browser-truth behavior that unit tests cannot: `packages/tailwindcss/tests/ui.spec.ts` (~2,480 lines) asserts *computed* values such as `touch-action` after `page.hover()` and resolved `linear-gradient(... in oklab ...)` strings. — [packages/tailwindcss/tests/ui.spec.ts#L1-L45](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/tests/ui.spec.ts#L1-L45) [packages/@tailwindcss-browser/tests/ui.spec.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-browser/tests/ui.spec.ts)
- The Playwright config sets Firefox `ui.primaryPointerCapabilities`/`ui.allPointerCapabilities` to `0x02 | 0x04` because headless Firefox otherwise reports no pointer, which would silently break every `hover:` variant assertion relying on `(hover: hover)`. — [packages/tailwindcss/playwright.config.ts#L44-L70](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/playwright.config.ts#L44-L70)
- WebKit is skipped on Windows in both Playwright configs (a hanging-worker workaround) and the CI matrix pins macOS to `macos-15` for the unit job because Playwright 1.62+ dropped WebKit on macOS 14 and hangs rather than failing. — [packages/tailwindcss/playwright.config.ts#L36-L43](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/playwright.config.ts#L36-L43) [.github/workflows/ci.yml#L27-L32](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/ci.yml#L27-L32)
- CI has two independent workflows: `ci.yml` (build → lint → `pnpm test` → Playwright) and `integration-tests.yml` (a 6-way `integration` matrix crossed with the OS matrix), both notifying Discord via `secrets.DISCORD_WEBHOOK_URL` on `main` failures. — [.github/workflows/ci.yml](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/ci.yml) [.github/workflows/integration-tests.yml](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/integration-tests.yml)
- Windows and macOS runners are excluded on feature branches unless the PR body contains `[ci-all]` (or the author is `depfu[bot]`), so most PRs only get Linux coverage by default — a documented, deliberate trade-off. — [.github/workflows/ci.yml#L34-L42](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/ci.yml#L34-L42) [.github/CONTRIBUTING.md#L88-L94](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L88-L94)
- CI lint is effectively Linux-only *and* currently dead: the step is gated on `matrix.runner.os == 'ubuntu-latest'`, but the Linux matrix entry sets `os: namespace-profile-default`, so the condition never matches. — [.github/workflows/ci.yml#L24-L26](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/ci.yml#L24-L26) [.github/workflows/ci.yml#L97-L100](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/ci.yml#L97-L100)
- Benchmarks are a first-class suite run via `pnpm bench` (`vitest bench`), with `*.bench.ts` files for the CSS parser, candidate parser, AST, sorting, brace expansion, and `infer-data-type`; the Rust side has its own throughput harness in `crates/oxide/src/main.rs`. — [package.json#L39](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/package.json#L39) [packages/tailwindcss/src/css-parser.bench.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/css-parser.bench.ts) [packages/tailwindcss/src/candidate.bench.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/candidate.bench.ts) [crates/oxide/src/main.rs#L50-L65](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/main.rs#L50-L65)

## Risk map

Security surface is narrow but real: the compiler loads and executes arbitrary user JS via `@plugin`/`@config` through jiti, the upgrade CLI shells out to git and the package manager and rewrites source files in place, and the scanner walks the filesystem outside the repo root whenever `@source` points there. The larger operational risks are correctness-of-caching risks — every integration hand-rolls its own mtime-based invalidation, and the core `build()` explicitly assumes candidates are only ever added, never removed. Cross-platform fragility (Windows paths, symlinks, Bun/USERPROFILE) shows up repeatedly as inline workarounds, which is a reliable signal of where regressions land.
- `@plugin` and `@config` execute arbitrary user code: `loadModule` resolves the id and calls `importModule`, which falls back to `createJiti(...).import(path)` when native `import()` fails — so a `.ts` config file is transpiled and run in-process with full Node privileges. — [packages/@tailwindcss-node/src/compile.ts#L114-L190](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L114-L190) [packages/tailwindcss/src/compat/apply-compat-hooks.ts#L54-L90](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/apply-compat-hooks.ts#L54-L90)
- `globalThis.__tw_resolve` and `globalThis.__tw_load` are ambient global escape hatches consulted before every CSS and JS resolution; the standalone binary installs them to serve embedded assets, but any code loaded into the process can override module resolution by setting them. — [packages/@tailwindcss-node/src/compile.ts#L192-L200](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L192-L200) [packages/@tailwindcss-node/src/compile.ts#L228-L236](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L228-L236) [packages/@tailwindcss-standalone/src/index.ts#L11-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-standalone/src/index.ts#L11-L60)
- `@tailwindcss/upgrade` shells out with `execSync` to `git rev-parse --show-toplevel`, `git status --porcelain`, and `npm ls tailwindcss --json`, then calls `pkgManager.add(...)` to install packages — it mutates the user's dependency tree as part of a 'migration'. — [packages/@tailwindcss-upgrade/src/utils/git.ts#L1-L20](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/utils/git.ts#L1-L20) [packages/@tailwindcss-upgrade/src/utils/version.ts#L1-L45](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/utils/version.ts#L1-L45) [packages/@tailwindcss-upgrade/src/index.ts#L263-L285](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/index.ts#L263-L285)
- The upgrade tool's only safety net for in-place source rewriting is the `isRepoDirty()` check, which `--force` disables outright; after that it rewrites every matched CSS file and every template file via `writeFileSafely` with no backup. — [packages/@tailwindcss-upgrade/src/index.ts#L58-L70](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/index.ts#L58-L70) [packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/utils/write-file-safely.ts)
- Template rewriting is heuristic and acknowledged as such: `isSafeMigration` maintains hand-written regex denylists for Vue `v-if`, Alpine `wire:`, shadcn `variant:` props, Next.js `placeholder=`, and `$emit(` — any unlisted pattern risks a class-name rewrite inside a string that is not a class attribute. — [packages/@tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts#L1-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts#L1-L60) [packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts#L32-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/template/migrate.ts#L32-L60)
- File IO reaches outside the repo root by design: `SourceEntry::External` exists specifically to let `@source "../node_modules/my-lib"` bypass the default ignore rules, and `resolveJsId` extends module search with `process.env.NODE_PATH`. — [crates/oxide/src/scanner/sources.rs#L50-L62](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/sources.rs#L50-L62) [packages/@tailwindcss-node/src/compile.ts#L184-L190](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts#L184-L190)
- `build()` carries a documented soundness assumption — 'this currently assumes that we only add new candidates and never remove any' — appearing twice as an early-return condition, so a candidate disappearing from source will not shrink the emitted CSS without a full rebuild. — [packages/tailwindcss/src/index.ts#L777-L800](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L777-L800)
- Cache invalidation is duplicated four times with slightly different shapes: `Root.requiresBuild()` in Vite, the `context.mtimes` loop in PostCSS, `getRebuildStrategy` in the CLI, and per-loader `CacheEntry.mtimes` in webpack/Turbopack — a bug fixed in one will not propagate to the others. — [packages/@tailwindcss-vite/src/index.ts#L528-L543](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L528-L543) [packages/@tailwindcss-postcss/src/index.ts#L160-L200](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L160-L200) [packages/@tailwindcss-cli/src/commands/build/index.ts#L800-L840](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L800-L840) [packages/@tailwindcss-webpack/src/index.ts#L34-L58](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-webpack/src/index.ts#L34-L58)
- `@tailwindcss/webpack` and `@tailwindcss/turbopack` are near-verbatim copies of each other down to identical `LoaderOptions`, `CacheEntry`, `getCacheKey`, and `getContextFromCache` definitions — divergence between them is a live duplication hazard. — [packages/@tailwindcss-webpack/src/index.ts#L1-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-webpack/src/index.ts#L1-L60) [packages/@tailwindcss-turbopack/src/index.ts#L1-L60](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-turbopack/src/index.ts#L1-L60)
- The PostCSS plugin deliberately swallows errors — it `console.error`s and then throws `root.error(...)` after resetting `context.compiler = null`, with a comment that throwing directly 'will cause PostCSS to no longer watch for changes' — so genuine compile failures can surface as an empty stylesheet plus console noise. — [packages/@tailwindcss-postcss/src/index.ts#L314-L345](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/src/index.ts#L314-L345)
- The Vite plugin carries two hardcoded upstream-bug workarounds — skipping `addWatchFile` for the input file itself (Qwik) and ignoring `.svg` paths containing `#` or `?` (tailwindcss#16877) — meaning edits to those files silently do not trigger rebuilds. — [packages/@tailwindcss-vite/src/index.ts#L376-L392](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-vite/src/index.ts#L376-L392)
- Windows and symlink handling is a recurring fragility: the scanner canonicalizes via `dunce::canonicalize` with a cached parent-directory map, inserts both symlinked and canonical paths into `self.files`, and `scan_content` has to re-walk the tree to validate previously-unknown changed files. — [crates/oxide/src/scanner/mod.rs#L155-L226](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L155-L226) [crates/oxide/src/scanner/mod.rs#L395-L440](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L395-L440)
- The standalone build re-spawns itself as a nested Bun process with `USERPROFILE` blanked out to work around Bun binary downloads failing on Windows CI when Turborepo passes the variable through — a build-time landmine with no test coverage. — [packages/@tailwindcss-standalone/scripts/build.ts#L9-L21](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-standalone/scripts/build.ts#L9-L21)
- `packages/tailwindcss/src/feature-flags.ts` is an empty file still present in the tree — dead scaffolding that suggests a removed flag system. — [packages/tailwindcss/src/feature-flags.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/feature-flags.ts)
- `crates/ignore` is a vendored fork of BurntSushi's crate carrying local `# CHANGED:` edits (e.g. pinning `edition = "2024"` instead of inheriting from the workspace), so upstream security or correctness fixes require a manual re-vendor. — [crates/ignore/Cargo.toml#L1-L20](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/ignore/Cargo.toml#L1-L20) [crates/ignore/src/incremental.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/ignore/src/incremental.rs)
- The CLI watch path is a ~600-line function with no unit tests of its own — `getRebuildStrategy`, `watchDirectories`, `createWatchers`, and the polling loop are only exercised indirectly through `integrations/cli/index.test.ts`. (inferred) — [packages/@tailwindcss-cli/src/commands/build/index.ts#L685-L870](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L685-L870) [integrations/cli/index.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/integrations/cli/index.test.ts)

## Extension points

The system has three distinct extension tiers. Tier one is CSS-native: `@utility`, `@custom-variant`, and `@theme` register directly into the design system with no JS. Tier two is the JS plugin API, preserved wholesale from v3 in `compat/plugin-api.ts` and loaded via `@plugin`. Tier three is internal: the `Utilities` and `Variants` classes are plain registries with `static`/`functional` methods that core code and plugins call identically, and the upgrade tool has its own ordered `DEFAULT_MIGRATIONS` array. Adding a new template language to the scanner is a matter of one new pre-processor module plus one match arm.
- `Utilities` is the utility registry — `static(name, compileFn)` and `functional(name, compileFn, options)` push into a `DefaultMap<string, Utility[]>`, with a parallel `completions` map driving IntelliSense suggestions. — [packages/tailwindcss/src/utilities.ts#L94-L120](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/utilities.ts#L94-L120) [packages/tailwindcss/src/utilities.ts#L294-L400](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/utilities.ts#L294-L400)
- `Variants` is the variant registry, offering `static`, `functional`, and `fromAst(name, ast, designSystem)` (which powers `@custom-variant` with a body), and tracking `compounds`/`compoundsWith` bit flags that decide which variants may be composed with which. — [packages/tailwindcss/src/variants.ts#L41-L130](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/variants.ts#L41-L130)
- `@utility` is the CSS-native extension point: `createCssUtility(node)` turns the at-rule into a `(designSystem) => void` registration function, with explicit validation that functional utilities end in `-*` and that `@utility` is never nested. — [packages/tailwindcss/src/index.ts#L221-L256](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L221-L256) [packages/tailwindcss/src/utilities.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/utilities.ts)
- `@custom-variant` registrations are topologically sorted before application via `topologicalSort(customVariantDependencies, { onCircularDependency })`, so variants may reference each other and circular references produce a rendered CSS error rather than a stack overflow. — [packages/tailwindcss/src/index.ts#L680-L705](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L680-L705) [packages/tailwindcss/src/utils/topological-sort.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/utils/topological-sort.ts)
- `buildPluginApi` is the JS plugin extension surface, exposing `addBase`, `addVariant`, `matchVariant`, `addUtilities`, `matchUtilities`, `addComponents`, `matchComponents`, and a `theme()` accessor, with name validation that rejects non-single-class selectors and non-lowercase-leading utility names. — [packages/tailwindcss/src/compat/plugin-api.ts#L39-L80](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/plugin-api.ts#L39-L80) [packages/tailwindcss/src/compat/plugin-api.ts#L266-L380](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/plugin-api.ts#L266-L380)
- `@plugin` supports typed options passed as a declaration body — `CssPluginOptions` values are parsed from the declaration text into JSON primitives (`null`, `true`, numbers, comma-separated lists) before being handed to the plugin factory. — [packages/tailwindcss/src/compat/apply-compat-hooks.ts#L57-L90](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/apply-compat-hooks.ts#L57-L90)
- `applyCompatibilityHooks` is the single seam where all v3 behavior is grafted on — it registers `darkModePlugin`, `registerContainerCompat`, `registerLegacyUtilities`, `registerScreensConfig`, and `registerThemeVariantOverrides`, keeping legacy concerns out of core. — [packages/tailwindcss/src/compat/apply-compat-hooks.ts#L1-L25](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/apply-compat-hooks.ts#L1-L25) [packages/tailwindcss/src/compat/legacy-utilities.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/legacy-utilities.ts) [packages/tailwindcss/src/compat/container.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compat/container.ts)
- `DesignSystem.storage` is an explicit general-purpose extension slot — a `Record<symbol, unknown>` documented as requiring unique symbol keys to avoid collisions; the canonicalization code uses it via `prepareDesignSystemStorage` and `UTILITY_SIGNATURE_KEY`. — [packages/tailwindcss/src/design-system.ts#L60-L68](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/design-system.ts#L60-L68) [packages/tailwindcss/src/design-system.ts#L240-L250](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/design-system.ts#L240-L250) [packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts#L1-L12](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/template/migrate.ts#L1-L12)
- Adding a new template language to the scanner requires two edits: a new module in `crates/oxide/src/extractor/pre_processors/` re-exported from `mod.rs`, and a new extension arm in the `pre_process_input` match statement. — [crates/oxide/src/extractor/pre_processors/mod.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/extractor/pre_processors/mod.rs) [crates/oxide/src/scanner/mod.rs#L541-L562](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L541-L562)
- The extractor is itself a pluggable set of state machines all implementing the `Machine` trait — `ArbitraryPropertyMachine`, `ArbitraryValueMachine`, `NamedUtilityMachine`, `NamedVariantMachine`, `ModifierMachine`, `StringMachine`, and others — which `main.rs` can benchmark individually via `_run_machine::<T>`. — [crates/oxide/src/extractor/mod.rs#L8-L22](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/extractor/mod.rs#L8-L22) [crates/oxide/src/extractor/machine.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/extractor/machine.rs) [crates/oxide/src/main.rs#L18-L48](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/main.rs#L18-L48)
- The upgrade tool's template codemods are an ordered array `DEFAULT_MIGRATIONS: Migration[]` where each entry is `(designSystem, userConfig, rawCandidate) => string`; order is load-bearing, flagged by the inline comment that `migrateVariantOrder` must run before variant-modifying migrations. — [packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts#L27-L46](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/template/migrate.ts#L27-L46)
- CSS-level codemods are a parallel, separately-composed pipeline in `codemods/css/` (`migrate-at-apply`, `migrate-at-layer-utilities`, `migrate-import`, `migrate-media-screen`, `migrate-preflight`, `migrate-tailwind-directives`, `migrate-theme-to-var`, `migrate-variants-directive`), each with a co-located test file. — [packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/css/migrate.ts) [packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts)
- The CLI command table is a literal dispatch in `run()` — `command === 'build'` and `command === 'canonicalize'` — with options merged from `build.options()` and `sharedOptions`, so a new subcommand means a module exporting `options()`/`handle()` plus one branch. — [packages/@tailwindcss-cli/src/index.ts#L30-L79](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/index.ts#L30-L79) [packages/@tailwindcss-cli/src/commands/help/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/help/index.ts)
- `internal-example-plugin` is a workspace package existing purely as a plugin-resolution fixture, consumed as a devDependency by `@tailwindcss/postcss` tests. — [packages/internal-example-plugin/package.json](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/internal-example-plugin/package.json) [packages/@tailwindcss-postcss/package.json](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-postcss/package.json)

## Contributor handoff

Read the core compiler before touching anything: `packages/tailwindcss/src/index.ts` (`parseCss` → `compileAst` → `compile`), then `design-system.ts` and `compile.ts`. Almost every behavioral change belongs in `packages/tailwindcss` or `crates/oxide`, not in the integration packages — those are thin and near-duplicative on purpose. Conventions are enforced mechanically: Prettier config lives inline in the root `package.json` (no semicolons, single quotes, 100 columns, organize-imports), and `pnpm lint` runs `prettier --check` plus `turbo lint` (which is `tsc --noEmit` per package). The project explicitly does not accept feature PRs without prior discussion, and does not accept any PR without tests.
- Read first, in order: `packages/tailwindcss/src/index.ts` (the at-rule walk and `build()` contract), `packages/tailwindcss/src/design-system.ts` (the `DesignSystem` type and its caches), `packages/tailwindcss/src/compile.ts` (candidate → AST + sorting), and `crates/oxide/src/scanner/mod.rs` (the scan lifecycle). — [packages/tailwindcss/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts) [packages/tailwindcss/src/design-system.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/design-system.ts) [packages/tailwindcss/src/compile.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compile.ts) [crates/oxide/src/scanner/mod.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs)
- Setup is `pnpm install`, `rustup default stable`, `rustup target add wasm32-wasip1-threads`, then `pnpm build`; the WASM target is mandatory because `@tailwindcss/oxide`'s `build` script always runs `build:wasm` after `build:platform`. — [.github/CONTRIBUTING.md#L3-L24](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L3-L24) [crates/node/package.json#L48-L56](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/node/package.json#L48-L56)
- Day-to-day loop is `pnpm tdd` (Vitest watch with `--hideSkippedTests`); for browser or integration behavior you must run `pnpm build` first because both harnesses consume built artifacts. — [package.json#L34-L40](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/package.json#L34-L40) [.github/CONTRIBUTING.md#L26-L34](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L26-L34) [.github/CONTRIBUTING.md#L64-L82](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L64-L82)
- Before submitting: run `pnpm test` (cargo + vitest), `pnpm build && pnpm test:integrations`, `pnpm build && pnpm test:ui`, and `pnpm run lint`; `pnpm run format` auto-fixes style. — [.github/CONTRIBUTING.md#L52-L84](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L52-L84) [package.json#L26-L40](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/package.json#L26-L40)
- Code style is Prettier with `semi: false`, `singleQuote: true`, `printWidth: 100`, and `prettier-plugin-organize-imports`; `integrations/**/*.ts` additionally uses `prettier-plugin-embed` so embedded CSS/HTML template literals are formatted. — [package.json#L5-L25](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/package.json#L5-L25)
- Safe edit boundary: the core compiler must stay IO-free. Anything needing `fs`, module resolution, or Lightning CSS belongs in `@tailwindcss/node`, which is the only package allowed to be the bridge between core and the runtime. — [packages/tailwindcss/src/index.ts#L253-L281](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L253-L281) [packages/@tailwindcss-node/src/compile.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/compile.ts)
- A change to utility or variant output almost always needs a snapshot update in `packages/tailwindcss/src/utilities.test.ts` or `variants.test.ts`, plus a check of `packages/tailwindcss/src/__snapshots__/`; these files are enormous, so target specific test names rather than running the whole suite. — [packages/tailwindcss/src/utilities.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/utilities.test.ts) [packages/tailwindcss/src/variants.test.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/variants.test.ts) [packages/tailwindcss/src/__snapshots__](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/__snapshots__)
- Trap: output ordering is not alphabetical. It is governed by `GLOBAL_PROPERTY_ORDER` in `property-order.ts` combined with a BigInt variant bitmask; reordering that array silently reshuffles every generated stylesheet. — [packages/tailwindcss/src/property-order.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/property-order.ts) [packages/tailwindcss/src/compile.ts#L78-L112](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/compile.ts#L78-L112)
- Trap: `process.env.NODE_ENV !== 'test'` gates both the license banner comment prepended in `compileAst` and the Lightning CSS warning output, so CSS produced under test differs from production output. — [packages/tailwindcss/src/index.ts#L700-L706](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/tailwindcss/src/index.ts#L700-L706) [packages/@tailwindcss-node/src/optimize.ts#L70-L78](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/optimize.ts#L70-L78)
- Trap: the integration packages duplicate the same source-derivation block (`root === 'none'` / `root === null` / explicit root, concat `compiler.sources`). Changing that contract in core requires updating the CLI, Vite, PostCSS, webpack, and Turbopack call sites together. — [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) [packages/@tailwindcss-webpack/src/index.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-webpack/src/index.ts)
- Trap: Rust edits need `pnpm build` (or `pnpm --filter @tailwindcss/oxide build`) to regenerate the `.node` addon before JS tests see them; `turbo.json` declares the cross-crate `inputs` that make this cache correctly. — [turbo.json](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/turbo.json) [crates/node/package.json#L48-L56](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/node/package.json#L48-L56)
- Debugging hook: set `DEBUG=1` (or `DEBUG=tailwindcss`) to enable the `Instrumentation` timers that bracket every phase — 'Setup compiler', 'Scan for candidates', 'Build CSS', 'Optimize CSS' — across the CLI, Vite, PostCSS, and loaders. — [packages/@tailwindcss-node/src/env.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/env.ts) [packages/@tailwindcss-node/src/instrumentation.ts](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-node/src/instrumentation.ts) [packages/@tailwindcss-cli/src/commands/build/index.ts#L118-L128](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/packages/%40tailwindcss-cli/src/commands/build/index.ts#L118-L128)
- Debugging hook for the scanner: `crates/oxide/src/scanner/init_tracing.rs` gates `tracing` events behind a `SHOULD_TRACE` lazy static, logging both provided and optimized source entries at INFO level. — [crates/oxide/src/scanner/init_tracing.rs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/init_tracing.rs) [crates/oxide/src/scanner/mod.rs#L95-L115](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/crates/oxide/src/scanner/mod.rs#L95-L115)
- Process convention: PRs need a test plan and tests; feature PRs are likely to be closed without prior discussion in GitHub Discussions; adding `[ci-all]` to the PR body opts into the full Windows/macOS CI matrix. — [.github/CONTRIBUTING.md#L38-L50](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L38-L50) [.github/CONTRIBUTING.md#L86-L94](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/CONTRIBUTING.md#L86-L94) [.github/workflows/ci.yml#L34-L42](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/ci.yml#L34-L42)
- Do not hand-edit release plumbing: `scripts/version-packages.mjs`, `scripts/lock-pre-release-versions.mjs`, `scripts/release-channel.js`, and `scripts/pre-publish-optimizations.mjs` are driven by `prepare-release.yml`/`release.yml`, and npm trusted publishing validates the calling workflow filename. — [scripts/release-channel.js](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/scripts/release-channel.js) [scripts/version-packages.mjs](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/scripts/version-packages.mjs) [.github/workflows/release.yml#L228-L236](https://github.com/tailwindlabs/tailwindcss/blob/41d9cae8e53378d16087fcf359eb785c2fd42ce4/.github/workflows/release.yml#L228-L236)

Source repository: https://github.com/tailwindlabs/tailwindcss
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/tailwindcss.json

## Public product links

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