Composing components
Tailwind Variants allows you to easily compose components using the extend
prop or the resultant function.
Using the extend prop
The extend
prop allows you to extend the component including its variants
, slots
, defaultVariants
and compoundVariants
. It automatically merges the values of same keys and offers Typescript autocomplete.
Basic example
import { tv } from 'tailwind-variants';
const baseButton = tv({
base: [
'font-semibold',
'dark:text-white',
'py-1',
'px-3',
'rounded-full',
'active:opacity-80',
'bg-zinc-100',
'hover:bg-zinc-200',
'dark:bg-zinc-800',
'dark:hover:bg-zinc-800'
]
});
const buyButton = tv({
extend: baseButton,
base: [
'text-sm',
'text-white',
'rounded-lg',
'shadow-lg',
'uppercase',
'tracking-wider',
'bg-blue-500',
'hover:bg-blue-600',
'shadow-blue-500/50',
'dark:bg-blue-500',
'dark:hover:bg-blue-600'
]
});
return (
<div className="flex gap-3">
<button className={baseButton()}>Button</button>
<button className={buyButton()}>Buy button</button>
</div>
);
/**
* buyButton();
*
* Result:
* font-semibold dark:text-white py-1 px-3 active:opacity-80 text-sm text-white rounded-lg
* shadow-lg shadow-blue-500/50 uppercase tracking-wider bg-blue-500 hover:bg-blue-600
* dark:bg-blue-500 dark:hover:bg-blue-600
*/
Extending components with variants
Components with variants
will inherit their variants when composed.
import { tv } from 'tailwind-variants';
const baseButton = tv({
base: 'font-semibold text-white rounded-full active:opacity-80',
variants: {
color: {
primary: 'bg-blue-500 hover:bg-blue-700',
secondary: 'bg-purple-500 hover:bg-purple-700',
success: 'bg-green-500 hover:bg-green-700'
},
size: {
small: 'py-0 px-2 text-xs',
medium: 'py-1 px-3 text-sm',
large: 'py-1.5 px-3 text-md'
}
}
});
const myButton = tv({
extend: baseButton,
variants: {
isSquared: {
true: 'rounded-sm'
}
}
// custom button styles
});
myButton({ color: 'success', size: 'medium', isSquared: true });
/**
* Result:
* font-semibold text-white active:opacity-80 rounded-sm bg-purple-500 hover:bg-purple-700 py-1 px-3 text-sm
*/
You can also extend components with defaultVariants
and compoundVariants
.
import { tv } from 'tailwind-variants';
const baseButton = tv({
base: 'font-semibold text-white rounded-full active:opacity-80',
variants: {
color: {
primary: 'bg-blue-500 hover:bg-blue-700',
secondary: 'bg-purple-500 hover:bg-purple-700',
success: 'bg-green-500 hover:bg-green-700'
},
size: {
small: 'py-0 px-2 text-xs',
medium: 'py-1 px-3 text-sm',
large: 'py-1.5 px-3 text-md'
}
},
defaultVariants: {
color: 'primary',
size: 'medium'
},
compoundVariants: [
{
color: 'primary',
size: 'medium',
className: 'rounded-sm'
}
]
});
const myButton = tv({
extend: baseButton
// custom button styles
});
myButton();
/**
* Result:
* font-semibold text-white active:opacity-80 bg-blue-500 hover:bg-blue-700 py-1 px-3 text-sm rounded-sm
*/
You can override any of the inherited variants
, defaultVariants
and
compoundVariants
by passing them to the component.
Extending components with slots
Components with slots
will inherit their slots when composed.
import { tv } from 'tailwind-variants';
const cardBase = tv({
slots: {
base: 'md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-gray-900',
avatar:
'w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto drop-shadow-lg',
wrapper: 'flex-1 pt-6 md:p-8 text-center md:text-left space-y-4',
description: 'text-md font-medium',
infoWrapper: 'font-medium',
name: 'text-sm text-sky-500 dark:text-sky-400',
role: 'text-sm text-slate-700 dark:text-slate-500'
}
});
const myCard = tv({
extend: cardBase
// custom card styles
});
const { base, avatar, wrapper, description, infoWrapper, name, role } =
myCard();
return (
<figure className={base()}>
<img
className={avatar()}
src="/intro-avatar.png"
alt=""
width="384"
height="512"
/>
<div className={wrapper()}>
<blockquote>
<p className={description()}>
“Tailwind variants allows you to reduce repeated code in your project
and make it more readable. They fixed the headache of building a
design system with TailwindCSS.”
</p>
</blockquote>
<figcaption className={infoWrapper()}>
<div className={name()}>Zoey Lang</div>
<div className={role()}>Full-stack developer, NextUI</div>
</figcaption>
</div>
</figure>
);
You can override any of the inherited slots by passing a new value to the slot key.
Using the result
You can use the result of the tv()
function to compose your components. However this method is not type-safe and you will have to use the class
/ className
prop to pass the result to the new component.
You can utilize either the base
key, slots
, or variants
when composing components using the result string. The key you use should be in the form of a string array.
import { tv } from 'tailwind-variants';
const baseButton = tv({
base: 'font-medium text-sm px-3 py-1 bg-blue-500 text-white rounded-full active:opacity-80'
});
const actionButton = tv({
base: [baseButton(), 'bg-red-500', 'rounded-xs']
});
actionButton();
/**
* Result:
* font-medium text-sm px-3 py-1 text-white active:opacity-80 bg-red-500 rounded-xs
*/
It's important to put the base styles first, so they can be overwritten by the other styles.