---
title: "Migrating from Tailwind Variants"
description: "Map tv to uv, drop merge config and lite, and stop expecting conflicting classes to disappear."
canonical_url: "https://vinicunca.dev/unocss-variants/guides/migration"
---
# 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

```diff
- 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**.

```ts
// 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](https://vinicunca.dev/unocss-variants/concepts/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).


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
