tailwindlabs/tailwindcss
@tailwindcss/upgrade
Active contributors: Robin, Philipp, Jordan
Purpose
The migration tool. npx @tailwindcss/upgrade walks an existing v3 codebase and rewrites everything — CSS, templates, JS configs, PostCSS configs — into the v4 equivalents. It is a bin-publishing package.
Directory layout
packages/@tailwindcss-upgrade/
├── package.json # bin: ./dist/index.mjs
├── README.md
├── tsconfig.json
├── tsup.config.ts
└── src/
├── index.ts # CLI entry (~460 lines)
├── stylesheet.ts # Stylesheet abstraction
├── index.test.ts
├── commands/
│ └── help/
│ └── index.ts
├── codemods/
│ ├── config/
│ │ ├── migrate-js-config.ts
│ │ └── migrate-postcss.ts
│ ├── css/
│ │ ├── analyze.ts
│ │ ├── format-nodes.ts
│ │ ├── link.ts
│ │ ├── migrate-at-apply.ts
│ │ ├── migrate-at-layer-utilities.ts
│ │ ├── migrate-config.ts
│ │ ├── migrate-import.ts
│ │ ├── migrate-media-screen.ts
│ │ ├── migrate-missing-layers.ts
│ │ ├── migrate-preflight.ts
│ │ ├── migrate-tailwind-directives.ts
│ │ ├── migrate-theme-to-var.ts
│ │ ├── migrate-variants-directive.ts
│ │ ├── migrate.ts
│ │ ├── sort-buckets.ts
│ │ └── split.ts
│ └── template/
│ ├── candidates.ts
│ ├── is-safe-migration.ts
│ ├── migrate-arbitrary-variants.ts
│ ├── migrate-automatic-var-injection.ts
│ ├── migrate-camelcase-in-named-value.ts
│ ├── migrate-canonicalize-candidate.ts
│ ├── migrate-handle-empty-arbitrary-values.ts
│ ├── migrate-legacy-arbitrary-values.ts
│ ├── migrate-legacy-classes.ts
│ ├── migrate-max-width-screen.ts
│ ├── migrate-modernize-arbitrary-values.ts
│ ├── migrate-prefix.ts
│ ├── migrate-simple-legacy-classes.ts
│ ├── migrate-theme-to-var.ts
│ ├── migrate-variant-order.ts
│ ├── migrate.ts
│ └── prepare-config.ts
└── utils/
├── args.ts
├── git.ts # isRepoDirty
├── packages.ts # detect package manager
├── version.ts # installed/expected Tailwind version
├── renderer.ts
└── write-file-safely.tsThree codemod categories
graph TD
Start[CLI entry] --> Cfg[Config codemods]
Cfg --> CSS[CSS codemods]
CSS --> Tpl[Template codemods]
Tpl --> Done[Final write]
subgraph "config/"
MJC[migrate-js-config]
MPC[migrate-postcss]
end
subgraph "css/"
MTD[migrate-tailwind-directives]
MAA[migrate-at-apply]
MAL[migrate-at-layer-utilities]
MIm[migrate-import]
MMS[migrate-media-screen]
MML[migrate-missing-layers]
MP[migrate-preflight]
MTV[migrate-theme-to-var]
MVD[migrate-variants-directive]
MC[migrate-config]
end
subgraph "template/"
TMC[migrate-canonicalize-candidate]
TLA[migrate-legacy-arbitrary-values]
TLC[migrate-legacy-classes]
TMP[migrate-prefix]
TVO[migrate-variant-order]
end
Cfg --> MJC --> MPC
CSS --> MTD --> MAA --> MAL --> MIm --> MMS --> MML --> MP --> MTV --> MVD --> MC
Tpl --> TMC --> TLA --> TLC --> TMP --> TVOThe actual order is more nuanced — the entry point src/index.ts orchestrates them — but the rough flow is "rewrite configs first (so CSS can resolve them), rewrite CSS (so we know what utilities to look for), then rewrite templates (which need the up-to-date utility set)."
Key entry behaviors
The top-level run() in src/index.ts:
- Verifies the git worktree is clean (
isRepoDirty()fromutils/git.ts). The--forceflag bypasses this. - Detects the installed Tailwind version (
utils/version.ts). Bails ifnode_modulesis out of sync withpackage.json. - Globs for stylesheets via
globbyand constructsStylesheetobjects (stylesheet.ts). - Runs
analyze,link, andmigrateover the CSS in turn. - Loads the v3
tailwind.config.js(if any) viaprepare-config.tsand feeds the resulting design system into the template migrators. - Globs source files (HTML, JSX, MDX, Vue, Svelte, etc.) via
globbyand runs each template codemod over them. - Migrates the PostCSS config last (because the user is likely re-running PostCSS afterwards).
- Writes everything via
write-file-safely.ts, which guards against killing the process mid-write (v4.2.3shipped a fix for files being emptied if the process was interrupted, seeCHANGELOG.md).
Safe migrations
packages/@tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts is a heuristic that decides whether a particular template token is safe to rewrite. The codemods only mutate strings the heuristic accepts. This is how the tool avoids breaking dynamically-constructed class names like bg-${color}-500.
The candidates passed through is-safe-migration come from the Rust scanner (@tailwindcss/oxide) — the upgrade tool reuses the same extractor the runtime build uses.
Canonicalization
The upgrade tool calls canonicalizeCandidate(...) (from packages/tailwindcss/src/canonicalize-candidates.ts) to produce the preferred shorthand form. The same logic backs the tailwindcss canonicalize CLI subcommand. Recent CHANGELOG entries describe lots of canonicalization improvements:
px-[1.2rem] py-[1.2rem]→p-[1.2rem]border-{t,b}-*→border-y-*start-full→inset-s-full[&:has(…)]→has-[…]-mt-[20in]→mt-[-20in]
Tests
Each codemod has a sibling *.test.ts with snapshot fixtures. The tool's top-level integration suite is in integrations/upgrade/.
Integration points
- Depends on
tailwindcss(for the canonicalizer + design system). - Depends on
@tailwindcss/node(for Node-side compilation when probing the v3 config). - Depends on
@tailwindcss/oxide(for the scanner, to know what candidates are present in templates). - Depends on
globby,picocolors,postcss,postcss-import,postcss-selector-parser,prettier,semver,tree-sitter,tree-sitter-typescript,enhanced-resolve,dedent,mri,jiti. Tree-sitter is used to parse JavaScript ASTs formigrate-js-config.ts.
Entry points for modification
- New CSS codemod: add a file under
codemods/css/and register it in the migration order insidesrc/index.ts. - New template codemod: add a file under
codemods/template/and register it intemplate/migrate.ts. - Tweak safety heuristics: edit
template/is-safe-migration.ts. Lots of edge cases live here. - Tweak which file extensions are scanned: the
globbypatterns are constructed insrc/index.ts.
The newest CHANGELOG entries for the upgrade tool include:
- v4.2.3: don't migrate inline
styleattributes (PR #19918), preserve files when killed mid-write (PR #19846), useconfig.contentwhen migrating, never migrate gitignored files, add.env/.env.*to default ignored content.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.