Migration Guide
v3.2.0 to v3.2.2
Breaking Changes
cn split into cn and cnMerge
In v3.2.2, the cn function has been simplified to eliminate Proxy complexity. The function now returns a string directly with default twMerge config. For custom twMerge configuration, use the new cnMerge function.
Before (v3.2.0 - v3.2.1):
import { cn } from 'tailwind-variants';
// cn could be called with config (using Proxy)
cn('px-2', 'px-4')({ twMerge: false }); // => "px-2 px-4"
cn('px-2', 'px-4')(); // => "px-4" (with default config)After (v3.2.2+):
import { cn, cnMerge } from 'tailwind-variants';
// cn now returns a string directly (no config support)
cn('px-2', 'px-4'); // => "px-4" (conflicts resolved with default config)
// cnMerge supports custom config via second function call
cnMerge('px-2', 'px-4')({ twMerge: false }); // => "px-2 px-4"
cnMerge('px-2', 'px-4')(); // => "px-4" (with default config)Migration:
- If you were using
cn(...)directly (most common case), no changes needed - it now works more intuitively by returning a string directly. - If you were using
cn(...)(config)orcn(...)(), migrate tocnMerge:// Before cn('px-2', 'px-4')({ twMerge: false }); // After cnMerge('px-2', 'px-4')({ twMerge: false });
Benefits:
- Better TypeScript support (no Proxy types)
- Clearer API intent
- Better performance (no Proxy overhead)
- Easier maintenance
v3.1.1 to v3.2.0
New Features
Introduction of cx function
A new lightweight utility function cx has been added for concatenating class names without tailwind-merge conflict resolution. This is useful when you don't need automatic conflict resolution and want a smaller bundle size.
// v3.2.0+
import { cx, cn } from 'tailwind-variants';
// cx - no conflict resolution
cx('text-blue-500', 'text-red-500'); // => "text-blue-500 text-red-500"
// cn - with conflict resolution (original build)
cn('text-blue-500', 'text-red-500'); // => "text-red-500"Breaking Changes
cnBase should be replaced with cx
In v3.2.0+, cnBase should be replaced with the new cx function. The cx function provides the same functionality as cnBase (simple concatenation without conflict resolution) with a clearer name.
Before (v3.1.1 and earlier):
import { cnBase } from 'tailwind-variants';
// ...
{cnBase("flex items-center justify-center gap-2", className)}After (v3.2.0+):
import { cx } from 'tailwind-variants';
// ...
{cx("flex items-center justify-center gap-2", className)}Migration: Replace all instances of cnBase with cx. The API is identical - both functions combine class names without conflict resolution.
cn now defaults twMerge to true
In v3.2.0+, the cn function in the original build now defaults twMerge to true and uses tailwind-merge for conflict resolution. This means conflicting Tailwind classes will be automatically resolved.
Before (v3.1.1 and earlier):
cn('text-blue-500', 'text-red-500'); // => "text-blue-500 text-red-500" (both classes)After (v3.2.0+):
cn('text-blue-500', 'text-red-500'); // => "text-red-500" (conflict resolved)Migration: If you relied on cn not merging conflicts, you can:
- Use the new
cxfunction instead:import { cx } from 'tailwind-variants' - Or continue using
cnif conflict resolution is desired (recommended)
defaultConfig exported as value
Starting in v3.2.0, defaultConfig is now exported as an actual value (not just a type), allowing direct import and modification:
// v3.2.0+
import { defaultConfig } from 'tailwind-variants';
defaultConfig.twMerge = false;This enables easier global configuration management.
responsiveVariants removed
The responsiveVariants option has been removed. For more details, see the Tailwind v4 documentation.
Bug Fixes
- Code is no longer minified in the build output (improves debugging experience)
v2 to v3
Breaking Changes
Introduction of /lite entry point (no tailwind-merge)
In v3, tailwind-variants is now offered in two builds:
- Original build – includes
tailwind-merge(same as before) - Lite build – excludes
tailwind-mergefor a smaller bundle and faster runtime
What changed?
tailwind-mergeis no longer lazily loaded; it's statically included in the original build only- Lite build completely removes
tailwind-mergeand its config createTV,tv, andcnin the lite build no longer acceptconfig(tailwind-merge config)- The
cnutility (previouslycnBase) is now exported from both builds with enhanced functionality
Migration Steps
If you use the default configuration with twMerge: true (conflict resolution enabled), make sure to install tailwind-merge in your project:
# npm
npm install tailwind-merge
# yarn
yarn add tailwind-merge
# pnpm
pnpm add tailwind-mergeIf you do not need conflict resolution, switch to the lite build by importing from tailwind-variants/lite:
import {createTV, tv, cn, cnBase} from "tailwind-variants/lite";Enhanced cn utility (formerly cnBase)
The cn utility has been significantly enhanced to support functionality similar to classnames / clsx. It now properly filters and handles various input types and formats the final result.
// v2
import {cnBase} from 'tailwind-variants';
// v3 - Original build (with tailwind-merge)
import {cn, cnBase} from 'tailwind-variants';
// v3 - Lite build (without tailwind-merge)
import {cn, cnBase} from 'tailwind-variants/lite';Both cn and cnBase are exported for backwards compatibility, but cn is the recommended import.
Performance Improvements
v3 includes significant performance improvements for projects using tailwind-merge:
| Test Case | v2 (ops/sec) | v3 (ops/sec) | Change |
|---|---|---|---|
| TV without slots & tw-merge | 1,397 | 572,465 | ⬆️ Up (~410x) |
| TV with slots & tw-merge | 692 | 306,297 | ⬆️ Up (~442x) |
| TV with custom tw-merge config | 700 | 359,269 | ⬆️ Up (~513x) |
Note: Performance for the lite build (without tw-merge) may be slightly lower due to enhanced
cnfunctionality, but this will be optimized in future updates.
Bundle Size Comparison
- Original build (
tailwind-variants): Includestailwind-mergestatically - Lite build (
tailwind-variants/lite): ~80% smaller withouttailwind-merge
v1 to v2
Breaking Changes
tailwind-merge is now an optional peer dependency
In v2, we've made tailwind-merge an optional peer dependency to reduce bundle size for users who don't need Tailwind CSS conflict resolution.
What changed?
tailwind-mergeis no longer bundled with tailwind-variants- Users who want conflict resolution must install it separately
- Users who don't need conflict resolution can save ~3KB in bundle size
Migration Steps
If you use the default configuration with twMerge: true (conflict resolution enabled):
# npm
npm install tailwind-merge
# yarn
yarn add tailwind-merge
# pnpm
pnpm add tailwind-mergeIf you don't need conflict resolution, disable it in your config:
const button = tv(
{
base: "px-4 py-2 rounded",
variants: {
color: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-500 text-white",
},
},
},
{
twMerge: false, // Disable conflict resolution
},
);Performance Improvements
v2 also includes significant performance optimizations:
- 37-62% faster for most operations
- Optimized object creation and array operations
- Reduced function call overhead
- Better memory usage
All existing APIs remain the same, so no code changes are required beyond the tailwind-merge installation.