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 /* ... */ });

On this page