Skip to Content
DocsConflict Resolution

Conflict Resolution

Tailwind Variants resolves conflicting Tailwind classes automatically on the default build. Conflict resolution is available on tv, createTV, cn, and cnMerge. It is not available on cx or the lite build.

Usage

import { tv, cn } from 'tailwind-variants'; cn('px-2', 'px-4'); // => "px-4" tv({ base: 'px-2', variants: { size: { lg: 'px-4' } } })({ size: 'lg' }); // => "px-4"
APIBehavior
tv / createTVMerges when twMerge is not false (default true on the default build)
cnJoins and merges with the default config. Returns a string.
cnMergeJoins and merges, with optional per-call twMerge / twMergeConfig
cxJoins only. Never merges.
/liteNo merge. Does not export cnMerge.
import { cx, cn, cnMerge } from 'tailwind-variants'; cx('px-2', 'px-4'); // => "px-2 px-4" cn('px-2', 'px-4'); // => "px-4" cnMerge('px-2', 'px-4')({ twMerge: false }); // => "px-2 px-4"

Custom twMergeConfig

Pass twMergeConfig to customize conflict resolution for your utilities. Prefer { extend, override }extend appends to the defaults, override replaces them.

Supported keys via extend / override: theme, classGroups, conflictingClassGroups, conflictingClassGroupModifiers, postfixLookupClassGroups, orderSensitiveModifiers.

Legacy flat fields on twMergeConfig (theme, classGroups, …) are still accepted and normalized into extend.

import { cnMerge, createTV, tv, type TWMergeConfig } from 'tailwind-variants'; const twMergeConfig = { extend: { classGroups: { elevation: ['elevation-low', 'elevation-high'] } } } satisfies TWMergeConfig; tv( { base: 'elevation-low', variants: { raised: { true: 'elevation-high' } } }, { twMergeConfig } ); createTV({ twMergeConfig }); cnMerge('elevation-low', 'elevation-high')({ twMergeConfig }); // => "elevation-high"

Disable merging with { twMerge: false } on tv, createTV, or cnMerge:

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

You can also set config globally with createTV or by updating defaultConfig. See the Config page for details.

Reusing a tailwind-merge config

If you already use extendTailwindMerge, reuse the same config object as twMergeConfig. Pass the object — not the merge function returned by extendTailwindMerge or createTailwindMerge:

import { extendTailwindMerge } from 'tailwind-merge'; import { createTV, type TWMergeConfig } from 'tailwind-variants'; const mergeConfig = { extend: { classGroups: { elevation: ['elevation-low', 'elevation-high'] } } } satisfies TWMergeConfig; extendTailwindMerge(mergeConfig); // app-level twMerge, if still needed createTV({ twMergeConfig: mergeConfig });

For createTailwindMerge factories, move the custom parts into { extend, override } and pass that object. Class-group validators (classPart: string) => boolean can be reused.

Do not pass:

  • The return value of extendTailwindMerge(...) or createTailwindMerge(...)
  • Full getDefaultConfig() results, merge functions, or factory functions

If tailwind-merge was only installed for Tailwind Variants, you can remove it after migrating configs. Keep it only if your app still calls twMerge, extendTailwindMerge, etc. directly.

Lite build

Import from tailwind-variants/lite when you do not need conflict resolution:

import { tv, createTV, cn, cx } from 'tailwind-variants/lite';
  • Lite tv / createTV do not apply merge or twMergeConfig
  • Lite does not export cnMerge
  • Lite cn is a curried no-merge adapter: cn(...)(config?) → string (the config is ignored)
Last updated on