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"| API | Behavior |
|---|---|
tv / createTV | Merges when twMerge is not false (default true on the default build) |
cn | Joins and merges with the default config. Returns a string. |
cnMerge | Joins and merges, with optional per-call twMerge / twMergeConfig |
cx | Joins only. Never merges. |
/lite | No 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(...)orcreateTailwindMerge(...) - 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/createTVdo not apply merge ortwMergeConfig - Lite does not export
cnMerge - Lite
cnis a curried no-merge adapter:cn(...)(config?)→ string (the config is ignored)