Migrating from Tailwind Variants

Map tv to uv, drop merge config and lite, and stop expecting conflicting classes to disappear.

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 VariantsUnoCSS 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 / defaultConfigNot implemented
tailwind-variants/liteNo /lite entry
cn that merges Tailwind conflictsSource cn is concat-only; npm 1.5.0 JS exports cnBase instead (also concat-only). No cnMerge
cxNot exported. Use cn / cnBase
cnBase (TV deprecated alias)Name lives on the stale npm bundle only
responsiveVariantsRemoved. Write sm: / md: in class strings
withTvUse 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:

  1. Add a uv layer in uno.config.ts.
  2. Prefix utilities that must win with uno-layer-uv:.
  3. Re-read every “Result” comment in your old TV docs or tests — they are wrong for uv if 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, not TVReturnType / UVReturnType.
  • compoundSlots is on the returned metadata object.
  • defaultVariants is a map of variant values, not of class strings.

Checklist

  1. Replace tv with uv and fix imports.
  2. Delete createTV, /lite, and merge config.
  3. Configure UnoCSS layers.uv and add uno-layer-uv: where parents and children clash.
  4. Import cnBase from npm 1.5.0 (or cn once the dist matches source).
  5. Update tests that expected merge (dropped px-2, dropped parent bg-*, and similar).