Development and Testing
Purpose and Scope
This page explains how to work on the Tailwind CSS repository as a contributor or operator: which package-manager scripts drive the common loops, how the monorepo build graph is coordinated, where unit and integration tests are separated, and how playgrounds fit into manual verification. Tailwind CSS is documented publicly as a zero-runtime utility-first CSS framework that scans project files for class names and writes generated CSS, so the repository workflow has to validate both compiler behavior and real build-tool integration paths. The development setup reflects that split: package tests run under Vitest, Rust engine tests run under Cargo, integration tests run from a dedicated root, and framework playgrounds provide hands-on smoke testing for product-facing installation flows.
Sources: package.json, turbo.json, vitest.config.ts, integrations/vitest.config.ts, playgrounds/nextjs/README.md
Relevant Source Files
package.json— Defines the root workspace scripts for formatting, linting, building, development, test execution, integration tests, UI tests, benchmarking, package versioning, and playground startup shortcuts.turbo.json— Defines the Turborepo task graph, cache behavior, build outputs, persistent development tasks, Rust native build inputs, and theRUSTUP_HOMEpass-through environment variable.vitest.config.ts— Defines the root Vitest project selection for package test suites and excludes integration tests from the default package-oriented test run.integrations/vitest.config.ts— Defines the Vitest project configuration used when running the integration test suite from theintegrationsworkspace root.playgrounds/nextjs/README.md— Documents the Next.js playground developer loop, including how to start the development server and which route to edit while manually testing framework behavior.
Workspace Script Model
The root package.json is the contributor-facing control surface. The scripts are intentionally organized around repeatable repository operations rather than one package at a time. format writes Prettier changes across the repository, while lint combines prettier --check . with turbo lint, so formatting and package lint tasks are treated as separate but sequential gates. build runs turbo build --filter=!./playgrounds/*, which means the normal build excludes playground packages. That distinction matters because playgrounds are developer verification environments, not artifacts that need to participate in every production package build.
The test commands separate fast package feedback from broader verification. The default test script runs cargo test before vitest run --hideSkippedTests, covering the Rust crates and the TypeScript package projects in one command. tdd keeps Vitest in the foreground for iterative work while still hiding skipped tests, and bench delegates to vitest bench for benchmark suites. Integration and UI validation are split into named scripts: test:integrations points Vitest at ./integrations, and test:ui runs package-specific UI test commands for tailwindcss and @tailwindcss/browser. This naming gives contributors a practical way to pick the narrowest loop that matches the change they are making.
Sources: package.json
Build Graph and Task Behavior
Turborepo coordinates package builds and development tasks through turbo.json. The generic build task depends on ancestor package builds with ^build, emits dist/**, and includes FEATURES_ENV in its environment contract. The generic dev task disables caching and is marked persistent, which matches long-running watchers and local development servers. That division is important for a monorepo containing compiler packages, integrations, and playgrounds: repeatable build artifacts can be cached, but interactive development processes should stay live and should not be replayed from cache.
The @tailwindcss/oxide tasks receive special treatment because the native engine sits across JavaScript and Rust boundaries. Its build and dev tasks declare native outputs such as index.d.ts, index.js, and *.node, and they track inputs from the package source, build.rs, package metadata, Cargo manifests, Cargo configuration, and the sibling Rust oxide source tree. The @tailwindcss/oxide#dev task also disables caching and is persistent. The global pass-through environment includes RUSTUP_HOME, which preserves Rust toolchain discovery when rustup is installed outside the default location. In practice, this means native package work should be performed through the root scripts so the expected Turborepo dependency and input model is applied consistently.
Sources: turbo.json
Test Suite Layout
The root Vitest configuration defines package-oriented testing. Its projects setting targets ./packages/* while excluding ./packages/tsconfig.base.json, so package workspaces are treated as independent Vitest projects. The same configuration excludes **/*.spec.?(c|m)[jt]s?(x) and integrations/**/* from that root package run. The important operational consequence is that integration tests are not accidental passengers in every package test run. Contributors changing compiler internals or package APIs can use the default Vitest path for package feedback, then opt into the integration suite when validating build-tool behavior.
The integration Vitest configuration is intentionally small and focused. It defines a project with hideSkippedTests: true, and the root script test:integrations invokes Vitest with --root=./integrations. This gives the integration suite its own working root, dependency assumptions, fixtures, and output behavior. When a change affects official installation paths such as Vite, PostCSS, CLI, framework guides, or browser workflows, running the integration suite is the right next step after package tests. When a change is localized to a utility parser, theme behavior, or package-level compiler API, starting with the root test or tdd scripts is usually the shorter feedback loop.
Sources: package.json, vitest.config.ts, integrations/vitest.config.ts
Playground Workflows
Playgrounds are excluded from the normal Turborepo build script, but they remain part of the developer experience through explicit root shortcuts. The root package.json includes vite and nextjs scripts that run filtered playground development servers, with nextjs targeting the nextjs-playground package. This mirrors the official documentation model where users are guided into framework and build-tool-specific installation paths. A playground lets contributors exercise those paths manually with a real application shell instead of only asserting package-level behavior.
The Next.js playground README follows the standard application loop: run the development server, open http://localhost:3000, and edit app/page.tsx to see the page update. It lists npm run dev, yarn dev, pnpm dev, and bun dev, but inside this repository the root shortcut is the more convenient entry point because it uses the workspace filter. The README also notes that the project uses next/font to optimize and load Inter, which means manual testing is not just a blank compiler harness; it reflects a framework project with realistic default framework behavior. Use this playground when validating that Tailwind-generated CSS participates correctly in a Next.js app during local development.
Sources: package.json, playgrounds/nextjs/README.md
Practical Contributor Flows
For formatting-only or documentation-adjacent changes, run pnpm format before submitting and use pnpm lint when you want the formatting check plus package lint graph. For TypeScript package changes, start with pnpm tdd while iterating, then use pnpm test before considering the change complete so both Cargo and Vitest coverage run through the root contract. For native engine or Oxide-adjacent work, prefer pnpm build or pnpm dev over ad hoc package commands because turbo.json encodes the native inputs, outputs, and Rust environment assumptions needed for reliable rebuilds.
For integration-facing changes, use a staged approach. First, run the narrow package tests that cover the changed implementation. Next, run pnpm test:integrations if the change can affect Vite, PostCSS, CLI, or framework behavior. Finally, start a playground such as pnpm nextjs when the risk is product-facing and visual or framework-specific behavior needs inspection in a browser. This sequence keeps local feedback fast while still matching Tailwind CSS’s public contract: users install it through build tools and frameworks, write utility classes in templates or components, and expect generated CSS to appear reliably in the running application.
Sources: package.json, turbo.json, vitest.config.ts, integrations/vitest.config.ts, playgrounds/nextjs/README.md
Command Reference
| Task | Command | Use it when | Source |
|---|---|---|---|
| Format repository files | pnpm format | You want Prettier to rewrite files using the root formatting policy. | package.json |
| Check formatting and lint packages | pnpm lint | You need a pre-submit formatting check plus Turborepo lint tasks. | package.json |
| Build packages | pnpm build | You need package build artifacts while excluding playgrounds. | package.json, turbo.json |
| Start persistent package development tasks | pnpm dev | You need long-running monorepo development tasks outside playgrounds. | package.json, turbo.json |
| Run full default tests | pnpm test | You need Rust crate tests and package Vitest projects in one command. | package.json, vitest.config.ts |
| Run integration tests | pnpm test:integrations | You need the dedicated integration Vitest root. | package.json, integrations/vitest.config.ts |
| Run iterative Vitest | pnpm tdd | You want a fast TypeScript test loop with skipped tests hidden. | package.json |
| Run benchmarks | pnpm bench | You need Vitest benchmark execution. | package.json |
| Start the Next.js playground | pnpm nextjs | You want manual framework testing in the Next.js playground. | package.json, playgrounds/nextjs/README.md |
Next Steps
After choosing the right local loop, connect the result back to the affected subsystem. Package API work should be read alongside the package API pages, build-tool work alongside the Vite, PostCSS, Webpack, or CLI pages, and migration work alongside the upgrade-tool and upgrade-guide pages. If the change affects user-facing installation guidance, validate it with both the relevant integration test and at least one playground or framework-style app flow. The repository scripts are designed to make that progression explicit: package tests for implementation confidence, integration tests for build-tool confidence, and playgrounds for realistic developer-experience checks.