Extending
Compose component recipes with extend — inherit base styles, variants, slots, and defaults without copying definitions.
extend merges one recipe into another. Start from a base button, then add icon-only sizing or new variant tokens without copying the whole definition.
Basic extend
Pass a parent recipe via the extend option:
import { tv } from 'tailwind-variants';const baseButton = tv({ base: 'inline-flex cursor-pointer items-center justify-center rounded-full font-medium select-none transition-colors', variants: { variant: { primary: 'bg-zinc-900 text-white', secondary: 'border border-zinc-300 bg-zinc-50 text-zinc-900', tertiary: 'text-zinc-700' }, size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4 text-sm' } }, defaultVariants: { variant: 'primary', size: 'md' }});const iconButton = tv({ extend: baseButton, base: 'gap-2', variants: { iconOnly: { true: 'aspect-square px-0', false: '' } }});Merge behavior
extend deep-merges:
- base — concatenated
- slots — merged by key
- variants — merged by axis; new keys add axes, existing keys add values
- defaultVariants — child overrides parent for matching keys
- compoundVariants — arrays concatenated
- compoundSlots — arrays concatenated
The returned object exposes merged metadata: variants, variantKeys, defaultVariants, and slot keys reflect the full composed recipe.
Extending slotted recipes
Slots merge by key. Child recipes can add slots or override styles on inherited ones:
import { tv } from 'tailwind-variants';
const card = tv({
slots: {
base: 'rounded-xl border p-4',
title: 'text-sm font-medium',
description: 'text-sm text-zinc-500'
}
});
const mediaCard = tv({
extend: card,
slots: {
media: 'mb-3 aspect-video rounded-lg bg-zinc-100',
title: 'text-base font-semibold'
}
});
const { base, media, title, description } = mediaCard();mediaCard keeps base / description from card, adds media, and tightens title.
One parent at a time
You can only extend one recipe per definition. To combine multiple bases, call them manually or chain extends:
const extended = tv({ extend: baseButton });
const final = tv({ extend: extended /* ... */ });