Skip to content
Docs
Migration to v3 (new)

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) or cn(...)(), migrate to cnMerge:
    // 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:

  1. Use the new cx function instead: import { cx } from 'tailwind-variants'
  2. Or continue using cn if 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-merge for a smaller bundle and faster runtime
What changed?
  • tailwind-merge is no longer lazily loaded; it's statically included in the original build only
  • Lite build completely removes tailwind-merge and its config
  • createTV, tv, and cn in the lite build no longer accept config (tailwind-merge config)
  • The cn utility (previously cnBase) 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-merge

If 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 Casev2 (ops/sec)v3 (ops/sec)Change
TV without slots & tw-merge1,397572,465⬆️ Up (~410x)
TV with slots & tw-merge692306,297⬆️ Up (~442x)
TV with custom tw-merge config700359,269⬆️ Up (~513x)

Note: Performance for the lite build (without tw-merge) may be slightly lower due to enhanced cn functionality, but this will be optimized in future updates.

Bundle Size Comparison

  • Original build (tailwind-variants): Includes tailwind-merge statically
  • Lite build (tailwind-variants/lite): ~80% smaller without tailwind-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-merge is 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-merge

If 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.