Basic Variants
Each variant has a name and a set of options. Each option defines props to apply.
const Badge = styled("span", {
base: { className: "inline-flex items-center rounded-full font-medium" },
variants: {
color: {
gray: { className: "bg-gray-100 text-gray-800" },
red: { className: "bg-red-100 text-red-800" },
green: { className: "bg-green-100 text-green-800" },
blue: { className: "bg-blue-100 text-blue-800" },
},
size: {
sm: { className: "px-2 py-0.5 text-xs" },
md: { className: "px-2.5 py-0.5 text-sm" },
lg: { className: "px-3 py-1 text-base" },
},
},
});
// Mix and match
<Badge color="green" size="sm">Active</Badge>
<Badge color="red" size="lg">Error</Badge>
Boolean Variants
For toggle-style variants, just define true. You don’t need false: {}.
const Button = styled("button", {
variants: {
isDisabled: {
true: {
className: "opacity-50 cursor-not-allowed",
disabled: true,
},
},
isLoading: {
true: { className: "animate-pulse" },
},
},
});
<Button isDisabled>Can't click</Button>
<Button isLoading>Processing...</Button>
⚠️ Avoid naming variants after existing component props. Use isDisabled instead of disabled to prevent shadowing native props.
With context, you can use the special ["boolean"] syntax to get proper boolean type inference. See Context for details.
Default Variants
Set sensible defaults so users don’t have to specify everything.
const Button = styled("button", {
variants: {
size: {
sm: { className: "h-8" },
md: { className: "h-10" },
lg: { className: "h-12" },
},
variant: {
primary: { className: "bg-blue-600" },
secondary: { className: "bg-gray-200" },
},
},
defaultVariants: {
size: "md",
variant: "primary",
},
});
// All of these render the same:
<Button />
<Button size="md" />
<Button variant="primary" />
<Button size="md" variant="primary" />
Compound Variants
Sometimes a specific combination of variants needs special styling. That’s what compoundVariants is for.
const Button = styled("button", {
variants: {
variant: {
solid: { className: "text-white" },
outline: { className: "bg-transparent border-2" },
},
color: {
blue: {},
red: {},
},
},
compoundVariants: [
// Solid + Blue
{
variant: "solid",
color: "blue",
props: { className: "bg-blue-600 hover:bg-blue-700" },
},
// Solid + Red
{
variant: "solid",
color: "red",
props: { className: "bg-red-600 hover:bg-red-700" },
},
// Outline + Blue
{
variant: "outline",
color: "blue",
props: { className: "border-blue-600 text-blue-600" },
},
// Outline + Red
{
variant: "outline",
color: "red",
props: { className: "border-red-600 text-red-600" },
},
],
});
The compound variant applies only when all specified conditions match.
When to Use Compound Variants
✅ Use compound variants when:
- Two variants interact visually - like
variant and color determining background AND text color together
- Specific combinations need exceptions - like adding a shadow only for large primary buttons
- You want to avoid CSS conflicts - compound variants let you be explicit about combinations
⚠️ Don’t overuse them. If every combination needs a compound variant, your design system might need restructuring.
Multiple Compound Variants
You can define multiple compound variants that apply simultaneously.
const Button = styled("button", {
variants: {
size: { sm: {}, lg: {} },
variant: { primary: {}, secondary: {} },
elevated: { true: {}, false: {} },
},
compoundVariants: [
// Large primary buttons get a shadow
{
size: "lg",
variant: "primary",
props: { className: "shadow-lg" },
},
// Elevated buttons get a border
{
elevated: true,
props: { className: "border-b-4" },
},
// Large elevated primary buttons get extra shadow
{
size: "lg",
variant: "primary",
elevated: true,
props: { className: "shadow-xl" },
},
],
});
// size="lg" + variant="primary" + elevated={true}
// Gets: shadow-lg (from first), border-b-4 (from second), shadow-xl (from third)
All matching compound variants apply. Class conflicts are resolved by tailwind-merge.
Variant Props vs Component Props
Variant props are removed before being passed to the underlying component. Only “real” props reach the DOM.
const Button = styled("button", {
variants: {
size: {
sm: { className: "h-8" },
lg: { className: "h-12" },
},
},
});
<Button size="lg" type="submit" aria-label="Submit form">
Submit
</Button>
// Rendered HTML:
// <button class="h-12" type="submit" aria-label="Submit form">Submit</button>
//
// Note: "size" doesn't appear in the DOM
This means you can use any variant name without worrying about HTML attribute conflicts.
Next: Context
Learn how variants propagate from parent to child