Releases

Notable Tailwind Variants releases and changes, including performance, merge, and API updates.

v4.0.1

Patch release. Restores numeric variant keys in props and VariantProps.

Fixes

  • Numeric variant keys. Options keyed by numbers, such as spacing scales { 0: "gap-0", 2: "gap-2" }, accept number literals again in tv() props and VariantProps, as in 3.3.1 (#318). Runtime was unaffected (Numeric keys)

v4.0.0

Major release (#306). Compiled merge tables, new /merge and /config entries, multi-extend, experimental debug, and a lite inject path for apps that already have a merger. Faster invocations and more consistent returns.

Highlights

  • Compiled merge tables. The default entry exports twMerge, twJoin, and clsx next to tv
  • tailwind-variants/merge. The merger without tv
  • tailwind-variants/config. twMergeConfig plus extendTailwindMerge, createTailwindMerge, createTwMerge, fromTheme, validators, mergeConfigs, and getDefaultConfig
  • Multi-extend. extend accepts a non-empty array of parents, merged left to right (Extending)
  • Debug mode (experimental). debug: true logs how each invocation resolves; development and browser only (Debugging)
  • Consistent returns. tv(), cn(), cnMerge(), and cx() return "" instead of undefined for empty class lists
  • Base-slot targeting. Array classes in compoundVariants and array variant values in slotted recipes apply to the base slot, matching string behavior
  • Composition fixes. Plain and slotted recipes compose cleanly: string classes fold into the base slot instead of producing [object Object]
  • Performance. Hot tv() about 3.5× vs 3.3.1 (slots hot about 4×). Matrix and no-merge calls about 4×. Compound-heavy calls about 5×, many compound variants over 6×. cn() about 2× on join-and-merge and unique arbitrary values, over 7× on interpolated arguments
  • Compounds guard. Unchanged compoundVariants and compoundSlots reuse their cached result instead of being re-resolved on every call (#317)
  • compoundSlots without variants. Multiple compoundSlots entries no longer break type inference when a recipe has no variants
  • Lite inject. createTV({ twMerge }) and createCN({ twMerge }) on /lite (Configuration)
  • twMergeConfig knobs. cacheSize and prefix are applied on the config entry
  • Merge fixes. Unlabeled font-[Inter] is a family and font-[700] a weight. contain-*, bg-gradient-to-*, bg-conic, columns-auto, max-h-none, and extra size scales merge correctly
  • Custom animations. As in 3.3.1 and tailwind-merge, only animate-spin, animate-ping, animate-pulse, and animate-bounce conflict by default. Custom names such as animate-wiggle and plugin classes such as animate-in are kept alongside them. Declare custom names through twMergeConfig extend.theme.animate on tailwind-variants/config

Multi-extend

Compose several parent recipes in one definition. Parents merge at definition time, so invocation speed is unaffected:

const button = tv({
  extend: [baseButton, focusable, animated],
  base: "inline-flex items-center",
});

Breaking changes

  • twMergeConfig moved to tailwind-variants/config. The default entry throws when a recipe is defined with it. Import tv, createTV, or cnMerge from the config entry, or inject a merger from createTwMerge through twMerge
  • experimentalParseClassName removed. Compiled tables cannot run a per-class parse hook. Keep tailwind-merge and inject it through twMerge
  • cn drops a numeric 0. cn("a", 0) is "a". cn(0) is "0". cx keeps 0
  • Empty string instead of undefined. tv(), cn(), cnMerge(), and cx() return "" when no classes remain, and CnReturn is string. Use falsy checks instead of === undefined. Patterns like className={cn(...) || fallback} keep working
  • Stricter type checking. Unknown or misspelled keys in variants, compoundVariants, and defaultVariants, and unknown slot names in slot-shaped class values, fail at compile time
  • Array classes target the base slot. In slotted recipes, array-based compoundVariants classes and array variant values previously never reached the base slot. Audit slotted recipes that relied on the old behavior
  • Peers dropped. tailwind-merge and tailwindcss are no longer peer dependencies
  • Lite function twMerge is invoked. In 3.3.1 a function was treated as true
  • twMerge: false joins with cx. Class strings are whitespace-normalized once, when a recipe is first used. On the default entry this turns merging off at runtime and does not drop the table. Use /lite for that

See Migration for upgrade steps.

v3.3.1

Patch release fixing slots shared-state contamination (#305).

v3.3.0

Stronger TypeScript inference, built-in conflict resolution, and less setup than earlier v3 releases.

Highlights

  • Fully rewritten in TypeScript — stronger inference for variants, slots, and extend
  • Conflict resolution included — no tailwind-merge install required for TV
  • Performance improvements — faster recipe resolution and class merging
  • Optional tailwindcss peer — use TV outside Tailwind-only setups more easily

Conflict resolution

The default tailwind-variants entry includes merge logic inspired by tailwind-merge. Install one package:

npm install tailwind-variants
  • Existing twMergeConfig objects generally keep working — prefer { extend, override }
  • Lite (tailwind-variants/lite) still has no merge
  • Utilities: tv, createTV, cn, cnMerge, cx

Behavior changes

  • slots: {} — explicit empty object enables slot mode with implicit base; omit slots for string return
  • Extended metadata types — describe merged variants, slots, and keys at runtime
  • cn(...)(config) — use cnMerge instead (see Migration)

Upgrade steps

  1. Remove tailwind-merge if it was only installed for TV
  2. Keep twMergeConfig as-is; use extend / override for custom tokens
  3. Switch to /lite only when you intentionally skip merge
- npm i tailwind-variants tailwind-merge
+ npm i tailwind-variants

Acknowledgements

Conflict resolution draws on MIT-licensed work from tailwind-merge, clsx, and cnfast. See Acknowledgements.


Earlier v3 releases

v3.2.2

  • cn returns a string directly; cnMerge added for custom config
  • Better TypeScript support without Proxy types

v3.2.0

  • cx replaces cnBase for lightweight concat
  • cn defaults to conflict resolution on the default build

v3.0.0

  • Default vs lite build split
  • Major performance improvements
  • Framework-agnostic API retained

See Migration for step-by-step upgrade paths.

On this page