FAQ
Answers to common questions about licensing, default vs lite builds, tailwind-merge, slots, TypeScript, and frameworks.
Is Tailwind Variants free?
Yes. MIT licensed. Use it in commercial and open-source projects.
Default build or lite?
Default (tailwind-variants) when you want automatic Tailwind conflict resolution. Included since v3.3, no extra install.
Lite (tailwind-variants/lite) when you do not need the table, or you already have twMerge.
import { tv } from 'tailwind-variants'; // built-in merge
import { tv } from 'tailwind-variants/lite'; // no tableInject a merger on lite with createTV / createCN. See Configuration.
Do I need tailwind-merge?
No, not for the default build on v3.3+. Merge is included.
Keep tailwind-merge if your app imports it directly (twMerge, extendTailwindMerge, etc.). On /lite, inject that function with createTV / createCN.
Why slots: {}?
An explicit empty slots object enables slot mode with a single implicit base slot:
const x = tv({ slots: {}, base: 'p-4' });
x().base(); // slot function
const y = tv({ base: 'p-4' });
y(); // plain string — omit slots entirelyOmit slots when you do not need slot functions.
cn vs cx vs cnMerge?
| Merge conflicts | Returns | Build | |
|---|---|---|---|
cx | No | String | Default + lite |
cn | Yes (default config) | String | Default |
createCN | Yes (injected) | String | Lite |
cnMerge | Yes (custom config) | Curried | Default |
cx('text-blue-500', 'text-red-500'); // both classes
cn('text-blue-500', 'text-red-500'); // "text-red-500"
cnMerge('px-2', 'px-4')({ twMerge: false }); // "px-2 px-4"Use cn for everyday merge. Use cx when classes cannot conflict. On lite, use createCN to inject a merger. Use cnMerge for per-call config.
Framework support?
Tailwind Variants is framework-agnostic. It returns class strings — use with any framework or vanilla JS. See Quick Start for binding examples.
Can I extend multiple components?
Yes. Pass a non-empty array and parents merge left to right:
const button = tv({ extend: [baseButton, focusRing, pressable] });On earlier versions, chain extends or compose class strings manually. See Extending.
Why did a class disappear from the output?
Conflict resolution probably merged it away: the last conflicting utility wins (Class Resolution). To see exactly which classes were dropped, enable the experimental debug mode in development:
const button = tv({ /* ... */ }, { debug: true });TypeScript not inferring variants?
Define variants inline inside tv, or use as const on external objects:
const variants = { primary: 'bg-zinc-900 text-white', secondary: 'bg-zinc-100' } as const;
const button = tv({ variants: { variant: variants } });Still stuck?
Ask in Discord or open a GitHub Discussion.