Skip to content

Migration from Tailwind Variants (tailwind-variants)

css-variants provides a similar API to Tailwind Variants, making migration straightforward. Both libraries support slots, variants, and compound variants.

FeatureTailwind Variantscss-variants
Function nametvcv (single) / scv (slots)
Slots definitionslots object in tvSeparate scv function
Compound class keyclassclassName
Compound slotscompoundSlotsUse compoundVariants with slot keys
Built-in tw-mergeYes (default)No (use classNameResolver)
Component compositionextend propertyNot built-in
Style variantsNot built-insv and ssv functions
import { tv } from 'tailwind-variants'
const button = tv({
base: 'font-semibold rounded-lg transition-colors',
variants: {
color: {
primary: 'bg-blue-600 text-white',
secondary: 'bg-gray-200 text-gray-900',
},
size: {
sm: 'px-3 py-1 text-sm',
lg: 'px-6 py-3 text-lg',
},
},
defaultVariants: {
color: 'primary',
size: 'sm',
},
})
const button = tv({
base: 'btn',
variants: {
color: { primary: '...', secondary: '...' },
size: { sm: '...', lg: '...' },
},
compoundVariants: [
{
color: 'primary',
size: 'lg',
class: 'shadow-lg font-bold', // TV uses 'class'
},
],
})

In Tailwind Variants, slots are defined within the tv function. In css-variants, use the separate scv function for multi-element components.

import { tv } from 'tailwind-variants'
const card = tv({
slots: {
root: 'rounded-lg border bg-white',
header: 'border-b p-4 font-semibold',
content: 'p-4',
footer: 'border-t p-4',
},
variants: {
variant: {
default: {
root: 'border-gray-200',
},
primary: {
root: 'border-blue-200',
header: 'bg-blue-50',
},
},
},
defaultVariants: {
variant: 'default',
},
})
// Usage
const { root, header, content, footer } = card({ variant: 'primary' })

Tailwind Variants has a dedicated compoundSlots feature. In css-variants, achieve the same result using compoundVariants with slot-specific classes.

const card = tv({
slots: {
root: 'rounded-lg',
header: 'p-4',
footer: 'p-4',
},
variants: {
color: {
primary: {},
secondary: {},
},
},
compoundSlots: [
{
slots: ['header', 'footer'],
color: 'primary',
class: 'bg-blue-100',
},
],
})
button({ color: 'primary', class: 'mt-4' })

Tailwind Variants includes tailwind-merge by default. css-variants is dependency-free but supports custom class merging via classNameResolver.

import { tv } from 'tailwind-variants'
// tw-merge is enabled by default
const button = tv({
base: 'p-4',
variants: {
size: {
sm: 'p-2', // tw-merge handles p-4 vs p-2 conflict
},
},
})
import { tv, type VariantProps } from 'tailwind-variants'
const button = tv({ /* ... */ })
type ButtonProps = VariantProps<typeof button>

Tailwind Variants supports extending components with the extend property. css-variants doesn’t have this built-in, but you can achieve similar results by defining shared configuration:

// Define shared configuration
const baseButtonConfig = {
base: 'rounded font-medium',
variants: {
size: { sm: 'px-2 py-1', lg: 'px-4 py-2' },
},
} as const
const baseButton = cv(baseButtonConfig)
// Extend by spreading and overriding
const primaryButton = cv({
base: [baseButtonConfig.base, 'bg-blue-600 text-white'],
variants: {
...baseButtonConfig.variants,
// Add or override variants
},
})

After migrating, you can take advantage of features not available in Tailwind Variants:

import { sv } from 'css-variants'
const box = sv({
base: { display: 'flex', borderRadius: '8px' },
variants: {
size: {
sm: { padding: '8px' },
lg: { padding: '24px' },
},
},
})
// Returns CSS style object instead of class string
box({ size: 'lg' }) // => { display: 'flex', borderRadius: '8px', padding: '24px' }
  • Replace tv imports with cv from css-variants
  • For components with slots, use scv instead and define slots as an array
  • Move slot base styles from slots object to base object
  • Change class to className in compound variants
  • Change class to className in runtime overrides
  • Convert compoundSlots to compoundVariants with slot-keyed className
  • If using tw-merge, add classNameResolver: twMerge to your config
  • Update type extraction from VariantProps<> to Parameters<typeof fn>[0]
  • (Optional) Convert inline style components to use sv or ssv