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 intv()props andVariantProps, 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, andclsxnext totv tailwind-variants/merge. The merger withouttvtailwind-variants/config.twMergeConfigplusextendTailwindMerge,createTailwindMerge,createTwMerge,fromTheme,validators,mergeConfigs, andgetDefaultConfig- Multi-extend.
extendaccepts a non-empty array of parents, merged left to right (Extending) - Debug mode (experimental).
debug: truelogs how each invocation resolves; development and browser only (Debugging) - Consistent returns.
tv(),cn(),cnMerge(), andcx()return""instead ofundefinedfor empty class lists - Base-slot targeting. Array classes in
compoundVariantsand array variant values in slotted recipes apply to thebaseslot, matching string behavior - Composition fixes. Plain and slotted recipes compose cleanly: string classes fold into the
baseslot 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
compoundVariantsandcompoundSlotsreuse their cached result instead of being re-resolved on every call (#317) compoundSlotswithoutvariants. MultiplecompoundSlotsentries no longer break type inference when a recipe has novariants- Lite inject.
createTV({ twMerge })andcreateCN({ twMerge })on/lite(Configuration) twMergeConfigknobs.cacheSizeandprefixare applied on the config entry- Merge fixes. Unlabeled
font-[Inter]is a family andfont-[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, onlyanimate-spin,animate-ping,animate-pulse, andanimate-bounceconflict by default. Custom names such asanimate-wiggleand plugin classes such asanimate-inare kept alongside them. Declare custom names throughtwMergeConfigextend.theme.animateontailwind-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
twMergeConfigmoved totailwind-variants/config. The default entry throws when a recipe is defined with it. Importtv,createTV, orcnMergefrom the config entry, or inject a merger fromcreateTwMergethroughtwMergeexperimentalParseClassNameremoved. Compiled tables cannot run a per-class parse hook. Keeptailwind-mergeand inject it throughtwMergecndrops a numeric0.cn("a", 0)is"a".cn(0)is"0".cxkeeps0- Empty string instead of
undefined.tv(),cn(),cnMerge(), andcx()return""when no classes remain, andCnReturnisstring. Use falsy checks instead of=== undefined. Patterns likeclassName={cn(...) || fallback}keep working - Stricter type checking. Unknown or misspelled keys in
variants,compoundVariants, anddefaultVariants, and unknown slot names in slot-shaped class values, fail at compile time - Array classes target the base slot. In slotted recipes, array-based
compoundVariantsclasses and array variant values previously never reached the base slot. Audit slotted recipes that relied on the old behavior - Peers dropped.
tailwind-mergeandtailwindcssare no longer peer dependencies - Lite function
twMergeis invoked. In 3.3.1 a function was treated astrue twMerge: falsejoins withcx. 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/litefor 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-mergeinstall required for TV - Performance improvements — faster recipe resolution and class merging
- Optional
tailwindcsspeer — 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
twMergeConfigobjects 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 implicitbase; omitslotsfor string return- Extended metadata types — describe merged variants, slots, and keys at runtime
cn(...)(config)— usecnMergeinstead (see Migration)
Upgrade steps
- Remove
tailwind-mergeif it was only installed for TV - Keep
twMergeConfigas-is; useextend/overridefor custom tokens - Switch to
/liteonly when you intentionally skip merge
- npm i tailwind-variants tailwind-merge
+ npm i tailwind-variantsAcknowledgements
Conflict resolution draws on MIT-licensed work from tailwind-merge, clsx, and cnfast. See Acknowledgements.
Earlier v3 releases
v3.2.2
cnreturns a string directly;cnMergeadded for custom config- Better TypeScript support without Proxy types
v3.2.0
cxreplacescnBasefor lightweight concatcndefaults 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.