tailwindlabs/tailwindcss
v3 compatibility
Tailwind v4 is a near-total rewrite, but the v3 plugin and config ecosystem keeps working. The bridge that makes that possible is packages/tailwindcss/src/compat/.
What "v3 compat" means
Three things in v3 that v4 needed to keep working:
- JavaScript config files (
tailwind.config.js,tailwind.config.ts). Loaded via@config "./tailwind.config.js"in CSS. - JavaScript plugins (
module.exports = require('tailwindcss/plugin')(({ addUtilities, matchUtilities, addVariant, addBase, addComponents, theme, e, prefix, config, ... }) => { … })). Loaded via@plugin "./plugin.js". - Re-exports like
tailwindcss/colors,tailwindcss/defaultTheme,tailwindcss/plugin,tailwindcss/lib/util/flattenColorPalette. These are still re-exported from the compat directory.
Directory layout
packages/tailwindcss/src/compat/
├── apply-compat-hooks.ts # Orchestrator (~520 lines)
├── plugin-api.ts # The v3 plugin API surface (~750 lines)
├── plugin-functions.ts # Helpers like `e`, `prefix`, `theme`, `config`
├── apply-config-to-theme.ts # config.theme → v4 Theme
├── apply-keyframes-to-theme.ts # config.theme.keyframes → v4 Theme
├── colors.ts / colors.cts # Default color palette (re-exported as tailwindcss/colors)
├── default-theme.ts / .cts # Default theme object (re-exported as tailwindcss/defaultTheme)
├── flatten-color-palette.ts/.cts # Helper re-exported as tailwindcss/lib/util/flattenColorPalette
├── legacy-utilities.ts # Utilities present only for v3 backwards compat
├── screens-config.ts # config.theme.screens → breakpoint variants
├── dark-mode.ts # config.darkMode → Tailwind v4 dark variant
├── container.ts # The v3 `container` component utility
├── theme-variants.ts # config.theme.* → CSS theme tokens
├── config/
│ └── types.ts # The full v3 config type
└── *.test.ts # TestsHow v3 configs become v4 design systems
graph LR
UCfg[User config file] -->|loaded by jiti| LM[loadModule]
LM --> ACH[applyCompatibilityHooks]
ACH --> ACT[apply-config-to-theme]
ACH --> AKT[apply-keyframes-to-theme]
ACH --> SC[screens-config]
ACH --> DM[dark-mode]
ACH --> Cont[container component]
ACH --> LU[legacy-utilities]
ACH -->|registers utilities/variants| DS[v4 DesignSystem]applyCompatibilityHooks is called from parseCss in packages/tailwindcss/src/index.ts after the design system is constructed but before utilities run. It runs through every @plugin and @config directive seen during parsing, executes them with the compat plugin API, and translates the resulting registrations into v4 form.
The compat plugin API
packages/tailwindcss/src/compat/plugin-api.ts implements the API v3 plugins are written against:
| v3 API | What it does in v4 |
|---|---|
addUtilities(obj) |
Registers each entry as a static utility on the v4 design system. |
matchUtilities(...) |
Registers as a functional utility, with the values map handled the v4 way. |
addComponents(obj) |
Registers as utilities at a different layer (so they sort below utilities). |
addBase(obj) |
Adds CSS to the @layer base cascade. |
addVariant(name, sel) |
Variants.static(...) registration. |
matchVariant(...) |
Variants.functional(...) registration. |
theme(path) |
Looks up the v4 Theme (with v3 paths translated) and returns the resolved value. |
config(path) |
Reads from the merged v3 config object. |
e(value) |
CSS escape — falls through to escape from packages/tailwindcss/src/utils/escape.ts. |
prefix(class) |
Adds the configured prefix to a class name (or returns it unchanged). |
PluginWithConfig is the type plugins implement. The CHANGELOG entry for v4.2.4 mentions "Export missing PluginWithConfig type from tailwindcss/plugin" (PR #19707) — exporting that type was needed for TypeScript users to type their plugin output correctly.
How @config and @plugin get loaded
Both are at-rules recognized by parseCss. They store the absolute path of the JS file in a list. After parsing, applyCompatibilityHooks walks the list and calls loadModule (provided by the adapter via @tailwindcss/node) for each. The loaded module's exports run through the compat plugin API.
The compat layer does not eagerly import any plugin files; it relies entirely on loadModule callbacks from the adapter. In @tailwindcss/browser, loadModule always throws — which is why the browser bundle does not support plugins or JS configs.
v3 quirks the compat layer preserves
- Class prefix. v3's
prefix: 'tw-'config translates to v4's@import 'tailwindcss' prefix(tw)syntax. The compat layer readsconfig.prefixand applies it accordingly. darkMode: 'class' | 'media' | 'selector'. Translated bydark-mode.tsinto the appropriate variant registration.screensshape. The v3screensconfig can be a record of named breakpoints, with min/max forms.screens-config.tstranslates these into v4@mediavariant registrations.flattenColorPalettequirks. Some v3 plugins callflattenColorPalette(theme.colors)to get a flat object of{ "red-500": "#…" }. The function is preserved inflatten-color-palette.ts.containercomponent.container.tsregisters it manually because v3'scontainerwas a special-case component, not a regular utility.legacy-utilities.ts— utilities that v3 had but v4 deprecated, kept around so compat templates still produce CSS.
What v3 plugins lose
addBase(obj)content is emitted into v4's base layer, but the precise cascade ordering relative to v4's own preflight may differ — plugins that relied on a specific ordering may need adjustment.- Plugins that mutated v4-only internals (none should, but defensive code patterns from v3 might) won't see them.
- Anything that was already deprecated in v3 (e.g. some long-removed CSS properties) is not re-introduced.
Testing
Compat layer tests are extensive:
compat/config.test.ts(~1,500 lines) — config loading, merging, and theme translation.compat/plugin-api.test.ts(~5,500 lines) — every helper in the plugin API.compat/screens-config.test.ts,compat/container-config.test.ts,compat/legacy-utilities.test.ts,compat/apply-config-to-theme.test.ts— focused tests per concern.
Adding new v3 compat coverage usually means adding a snapshot to one of these files.
For migration off v3 entirely see @tailwindcss/upgrade.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.