Class Resolution

How Tailwind Variants resolves and merges conflicting utilities with cn, cx, cnMerge, twMerge, twJoin, and clsx in the default and lite builds.

Tailwind Variants ships utilities for joining class strings. Pick the one that matches whether you need conflict resolution and how much bundle you can spend.

cn — merge with defaults

cn returns a string directly, merged with the default config. No second function call.

import { cn } from 'tailwind-variants';

cn('px-2 py-1', 'px-4');           // => "py-1 px-4"
cn('text-blue-500', 'text-red-500'); // => "text-red-500"
cn('text-sm', { 'font-bold': true }); // => "text-sm font-bold"

Use cn for the common case — concatenating classes with automatic Tailwind conflict resolution.

cx — lightweight concat

cx joins classes without resolving conflicts. Same role as clsx — smaller and faster when merge is unnecessary.

import { cx } from 'tailwind-variants';
// or
import { cx } from 'tailwind-variants/lite';

cx('text-blue-500', 'text-red-500'); // => "text-blue-500 text-red-500"
cx(['px-4', 'py-2'], { hidden: false }); // => "px-4 py-2"

Use cx inside lite builds, static strings with no overlap, or when you handle conflicts yourself.

cx vs clsx vs twJoin

The default and /merge entries also export twMerge, twJoin, and clsx. The three joiners differ:

UtilityObjectsNumeric 0Whitespace
cxYesKeptNormalized
clsxYesDropped among other argumentsAs given
twJoinNo, strings and arrays onlyDroppedAs given

cn is clsx followed by twMerge, so it drops a numeric 0 among other arguments. A lone 0 still stringifies:

import { cn, cx, clsx, twJoin } from 'tailwind-variants';

cn('foo', 0);     // => "foo"
cn(0);            // => "0"
cx('foo', 0);     // => "foo 0"
clsx('foo', 0);   // => "foo"
twJoin('foo', ['bar']); // => "foo bar"

Arbitrary font values

Unlabeled font-[…] follows Tailwind v4. font-[Inter] is a font family and font-[700] is a weight. Labeled and CSS-variable forms match tailwind-merge 3.6.0:

cn('font-sans font-[Inter]'); // => "font-[Inter]"
cn('font-bold font-[700]');   // => "font-[700]"
cn('font-sans font-[700]');   // => "font-sans font-[700]"

cnMerge — custom merge config

When you need per-call control over merge behavior, use cnMerge. It returns a function that accepts config:

import { cnMerge } from 'tailwind-variants';

cnMerge('px-2', 'px-4')(); // => "px-4" (default merge)

cnMerge('px-2', 'px-4')({ twMerge: false }); // => "px-2 px-4"

twMergeConfig needs the table compiler, so import cnMerge from the config entry for custom groups:

import { cnMerge } from 'tailwind-variants/config';

cnMerge('px-2', 'px-4')({
  twMerge: true,
  twMergeConfig: {
    extend: {
      classGroups: {
        'font-size': ['text-tiny']
      }
    }
  }
});

On the default build, if you used cn(...)(config) before v3.2.2, migrate to cnMerge. Default cn no longer accepts config.

Default vs lite

UtilityDefault buildLite build
cnString, with mergeCurried join. Bind a merger with createCN
cnMergeCustom merge configNot exported
cxConcat onlyConcat only
tvMerge enabled by defaultNo table. Inject with createTV({ twMerge })
twMerge, twJoin, clsxExported, also on /mergeclsx only, strings-only shape

Import from tailwind-variants for the built-in table. Import from tailwind-variants/lite when you skip it, or inject a function you already have. See Configuration.

twMerge on tv

The second argument to tv accepts config:

const button = tv(
  { base: 'px-2', variants: { size: { lg: 'px-4' } } },
  { twMerge: true }
);

twMergeConfig is accepted by tv from tailwind-variants/config only. See Configuration for shared defaults via createTV.

On this page