Migrating from Tailwind Variants
UnoCSS Variants is a port of Tailwind Variants for UnoCSS. Most recipes translate by renaming the import. The behavioral gap is no merge.
Rename the factory
- import { tv, type VariantProps } from 'tailwind-variants'
+ import { uv, type VariantProps } from 'unocss-variants'
- const button = tv({
+ const button = uv({
base: 'rounded-md px-3 py-1.5',
variants: { /* ... */ }
})
class and className overrides still work. extend, slots, compoundVariants, compoundSlots, and defaultVariants keep the same shape.
APIs that do not exist
| Tailwind Variants | UnoCSS Variants |
|---|---|
tv(options, config?) | uv(options) only — no second argument |
createTV({ twMerge, twMergeConfig }) | Not in source. npm 1.5.0 still exports a leftover createUv() with no config |
twMerge / twMergeConfig / defaultConfig | Not implemented |
tailwind-variants/lite | No /lite entry |
cn that merges Tailwind conflicts | Source cn is concat-only; npm 1.5.0 JS exports cnBase instead (also concat-only). No cnMerge |
cx | Not exported. Use cn / cnBase |
cnBase (TV deprecated alias) | Name lives on the stale npm bundle only |
responsiveVariants | Removed. Write sm: / md: in class strings |
withTv | Use uv directly |
Do not copy tv({ ... }, { twMerge: false }) — the second argument is ignored only because it is not in the type or runtime of uv. Pass a single options object.
Conflicts
Default Tailwind Variants drops earlier conflicting utilities. uv keeps all of them.
// tailwind-variants (default): "py-1 px-4"
// unocss-variants: "px-2 py-1 px-4"
uv({ base: 'px-2 py-1' })({ class: 'px-4' });
After migrating:
- Add a
uvlayer inuno.config.ts. - Prefix utilities that must win with
uno-layer-uv:. - Re-read every “Result” comment in your old TV docs or tests — they are wrong for
uvif they show dropped classes.
See Layers.
Slots
Non-empty slots still switch the return type to an object of functions. On the published 1.5.0 build, slots: {} does not enable an implicit-only base slot (TV v3 does). Pass at least one slot key, or use slots: { base: '...' }.
Root-level class on a slotted call is not applied to base() on that published build. Use base({ class }).
Types
VariantProps— same idea.- Return type name is
UvReturnType, notTVReturnType/UVReturnType. compoundSlotsis on the returned metadata object.defaultVariantsis a map of variant values, not of class strings.
Checklist
- Replace
tvwithuvand fix imports. - Delete
createTV,/lite, and merge config. - Configure UnoCSS
layers.uvand adduno-layer-uv:where parents and children clash. - Import
cnBasefrom npm 1.5.0 (orcnonce the dist matches source). - Update tests that expected merge (dropped
px-2, dropped parentbg-*, and similar).