Open-Source Wikis

/

Tailwind CSS

/

How to contribute

/

Patterns and conventions

tailwindlabs/tailwindcss

Patterns and conventions

Style

Prettier configuration is embedded in the root package.json:

{
  "prettier": {
    "semi": false,
    "singleQuote": true,
    "printWidth": 100,
    "plugins": ["prettier-plugin-organize-imports"]
  }
}

prettier-plugin-organize-imports reorganizes imports automatically. Don't fight it — your editor's "format on save" should be enabled.

For integrations/, an additional Prettier plugin (prettier-plugin-embed) formats embedded CSS/JS strings inside test fixtures.

Rust code uses default rustfmt plus the project rules in crates/node/rustfmt.toml.

Module shape

Most TypeScript files in the compiler follow one of three shapes:

  1. Function modules. A single primary exported function plus its helpers (e.g. apply.ts exports substituteAtApply; at-import.ts exports substituteAtImports; compile.ts exports compileCandidates).
  2. Type/data modules. Pure data exports (property-order.ts) or interfaces only (types.ts).
  3. Class-shaped modules. A few places use a class because they hold state across calls — Theme (theme.ts), Instrumentation (@tailwindcss/node).

The entry point of every package is src/index.ts. The compiler additionally exports index.cts and index.bench.ts for CommonJS consumers and benchmarks respectively.

AST conventions

The compiler's AST is defined in packages/tailwindcss/src/ast.ts:

type AstNode =
  | StyleRule // selector + nodes
  | AtRule // @-rule with optional nodes
  | Declaration // property: value
  | Comment
  | Context // metadata wrapper
  | AtRoot; // forces children to top-level

Construct nodes with the small helpers (styleRule, atRule, decl, comment, context, atRoot). The walk(ast, fn, ...) function in walk.ts is how you traverse, and it understands Context and AtRoot semantics. Replacing nodes happens through the WalkAction enum (Continue, Skip, Stop, Replace([...]), ReplaceSkip([...]), ReplaceStop([...])).

Error handling

The compiler throws plain Errors at the boundaries of bad input. For example, parseCss throws on malformed @theme, @source, or @utility directives. Adapter code (CLI, Vite, PostCSS, Webpack) wraps the calls in try/catch and reports through their host:

  • CLI: eprintln(err.toString()); process.exit(1) (packages/@tailwindcss-cli/src/commands/build/index.ts).
  • PostCSS: clears the cache entry on error (getCacheKeycache.delete(key)) so the next attempt retries from scratch.
  • Vite: lets the error bubble; Vite shows it in the browser overlay.
  • Webpack: passes the error to its async callback.

Errors from the v3 compat layer often include a hint about migration. Search the codebase for throw new Error( to see message patterns.

Naming

  • TypeScript files use kebab-case (compile.ts, at-import.ts, apply-compat-hooks.ts).
  • Test files are siblings with .test.ts (compile.test.ts).
  • Benchmarks are siblings with .bench.ts.
  • Snapshot directories live next to the test file (__snapshots__/).
  • Rust files use snake_case (candidate_machine.rs, auto_source_detection.rs).
  • Type names use PascalCase. Functions and variables use camelCase. Enums (Features, Polyfills, ThemeOptions, WalkAction, CompileAstFlags) are PascalCase with PascalCase members.

"Public" vs "private"

Inside the compiler package, anything exported from a module is fair game for sibling modules. The package's external API surface is what package.json's exports field exposes:

{
  ".": "./src/index.ts",
  "./colors": "./src/compat/colors.ts",
  "./defaultTheme": "./src/compat/default-theme.ts",
  "./plugin": "./src/plugin.ts",
  "./preflight.css": "./preflight.css",
  // ... and a few more
}

Anything not listed there is internal. Adapter packages reuse internal modules by import { … } from '../../tailwindcss/src/…' (e.g. @tailwindcss/postcss imports toCss from tailwindcss/src/ast). This is intentional — the workspace can cross package boundaries because it's a monorepo.

TypeScript strictness

tsconfig.base.json enables strict mode for every package. There is no JS emission target — tsc is used purely as a type checker via the per-package lint script:

{
  "scripts": { "lint": "tsc --noEmit" },
}

Bundling is handled by tsup-node (see Tooling).

Dependencies

The compiler package (packages/tailwindcss) has zero runtime dependencies. Only devDependencies. This is intentional — the browser bundle in @tailwindcss/browser includes the compiler at runtime, and it must not pull in magic-string or lightningcss there.

The Node-only optimizations (magic-string, lightningcss, source-map-js, enhanced-resolve, jiti) all live in @tailwindcss/node. Adapters depend on @tailwindcss/node, never directly on tailwindcss's devDependencies.

CSS conventions

  • The framework ships four CSS files at the package root: index.css, preflight.css, theme.css, utilities.css. They are exported via package.json exports.
  • index.css is the canonical entry point — it @imports the others.
  • theme.css contains all default theme tokens (~700 lines). Edits go here, not in TypeScript.
  • preflight.css is the global reset. It mirrors v3's preflight closely.

Cross-cutting helpers

A handful of small utilities are reused everywhere:

  • segment(str, sep) (packages/tailwindcss/src/utils/segment.ts) — splits a string on a separator while respecting brackets, parens, and quotes. The grammar is "split unless we're inside a balanced bracket pair."
  • escape(str) / unescape(str) (packages/tailwindcss/src/utils/escape.ts) — CSS identifier escaping.
  • compare(a, b) (packages/tailwindcss/src/utils/compare.ts) — natural sort that handles numeric parts.
  • expand(pattern) (packages/tailwindcss/src/utils/brace-expansion.ts) — a-{1,2,3}[a-1, a-2, a-3].
  • DefaultMap (packages/tailwindcss/src/utils/default-map.ts) — a Map that materializes a default on read.

When in doubt, search packages/tailwindcss/src/utils/ before writing your own.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Tailwind CSS wiki | Factory