tailwindlabs/tailwindcss
Development workflow
A typical session.
1. Get the repo building
git clone https://github.com/tailwindlabs/tailwindcss
cd tailwindcss
pnpm install
rustup default stable
rustup target add wasm32-wasip1-threads
pnpm buildThe pnpm build step is required at least once: it compiles the Rust crate into a Node addon (@tailwindcss/oxide), bundles every TypeScript package with tsup, and runs scripts/pack-packages.mjs to emit installable tarballs into dist/ for the integration tests.
2. Pick a package and run it in watch mode
The fastest dev loop depends on which package you're touching:
| You're working on… | Use this |
|---|---|
The compiler (packages/tailwindcss) |
pnpm tdd (Vitest watch) |
| The CLI | pnpm --filter @tailwindcss/cli dev |
| The Vite plugin | pnpm vite (drives playgrounds/vite) |
| The Next.js example | pnpm nextjs |
| The browser build | pnpm --filter @tailwindcss/browser dev |
| The Rust scanner | cargo watch -p tailwindcss-oxide -x test (if cargo-watch is installed) |
pnpm dev at the root spins up turbo dev across all packages but skips the playgrounds.
3. Write a failing test
Pick the closest existing test file:
- Compiler bug touching at-rule semantics → add to
packages/tailwindcss/src/index.test.ts. - Specific utility behavior → add to
packages/tailwindcss/src/utilities.test.ts. - Variant logic →
packages/tailwindcss/src/variants.test.ts. - Scanner extraction shape →
crates/oxide/src/extractor/*_machine.rstests at the bottom of each file. - Scanner walking/source detection →
crates/oxide/tests/scanner.rs. - Upgrade tool codemod → next to the codemod, e.g.
packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts. - Integration / real-bundler regression →
integrations/<bundler>/.
The test files use Vitest with expect.toMatchInlineSnapshot() heavily. Run with --update to refresh snapshots:
pnpm test -- --update4. Implement the fix
Code conventions are enforced by Prettier (semi: false, singleQuote: true, printWidth: 100) and prettier-plugin-organize-imports. Don't fight the import sorter — saves time later.
For TypeScript, types are checked via tsc --noEmit per package. There is no JS emit; bundling happens through tsup.
For Rust, formatting is rustfmt (configuration in crates/node/rustfmt.toml).
5. Run the local test suite
pnpm test # cargo test + vitest run
pnpm lint # prettier --check + per-package tscIf your change crosses the JS/Rust boundary, also run integration tests:
pnpm build # rebuild dist/ tarballs
pnpm test:integrationsIf your change can affect browser CSS resolution (custom properties, color-mix, @property), run the UI tests too:
pnpm test:uiThe UI test suites run via Playwright (packages/tailwindcss/playwright.config.ts, packages/@tailwindcss-browser/playwright.config.ts).
6. Document the change
Edit CHANGELOG.md under ## [Unreleased], in either ### Added, ### Fixed, or ### Changed. Match the existing style: a single-line bullet ending with a (#PR) link.
7. Open the PR
The pull request template (.github/PULL_REQUEST_TEMPLATE.md) asks for:
- A description of the change.
- A test plan.
- Confirmation that tests pass.
Maintainers are auto-assigned via .github/CODEOWNERS.
Tips
- Don't add new dependencies casually. Most packages have very few runtime dependencies (the compiler package has none). New deps need a justification.
- Use the playgrounds for sanity checks, but don't commit changes to them as part of your PR unless they're testing your fix.
- The compiler has zero runtime deps. That's intentional — it has to work in browser bundles via
@tailwindcss/browser. patches/is sacred. The@parcel/watcherandlightningcsspatches exist so the Bun-built standalone CLI can statically analyze them. If you bump those packages, re-verify the patches.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.