Compound Variants
Compound variants apply styles only when the variant values you list all match. They're the answer to "I want this style only when variant: 'outlined' and color: 'danger'." Listing a single axis is allowed too — it then behaves like a variant style that is applied after all the others.
compoundVariants
A compound variant applies styles only when multiple axes match specific values. Useful for "X looks different when it's also Y" patterns.
const button = ev({
base: {
border: '1px solid transparent',
borderRadius: 8,
cursor: 'pointer',
fontWeight: 600,
transition: 'all 0.2s ease',
},
variants: {
variant: {
filled: {
background: 'var(--button-color)',
color: 'white',
},
outlined: {
background: 'transparent',
borderColor: 'var(--button-color)',
color: 'var(--button-color)',
},
text: {
background: 'transparent',
color: 'var(--button-color)',
},
},
size: {
small: {
padding: '8px 16px',
fontSize: 12,
},
medium: {
padding: '10px 20px',
fontSize: 14,
},
large: {
padding: '12px 24px',
fontSize: 16,
},
},
color: {
primary: {
'--button-color': '#d36ac2',
},
secondary: {
'--button-color': '#475569',
},
},
},
compoundVariants: [
{
variants: {
variant: 'filled',
size: 'large',
color: 'primary',
},
style: {
boxShadow: '0 4px 12px rgba(211, 106, 194, 0.4)',
},
},
],
defaultVariants: {
variant: 'filled',
size: 'medium',
color: 'primary',
},
});
<button
css={button({
size: 'large',
color: 'primary',
})}
>
Large primary
</button>
<button
css={button({
size: 'large',
color: 'secondary',
})}
>
Large secondary
</button>Compound variants are evaluated in array order and stacked after the matching simple variants, so they can safely override.
When to reach for them
Use a compound variant when:
- A style depends on two or more axes simultaneously.
- Expressing the style as a single-axis variant would force you to invent a new axis or duplicate values.
- Conditional CSS inside the component (
size === 'lg' && color === 'primary' && ...) is creeping in.
If a style depends on only one axis, put it directly in variants — compound variants are for the cross-product.
Pattern: filled / outlined / text × color
A common design system pattern: the same set of colors styled three ways.
import { ev } from 'emotion-variants';
const button = ev({
base: {
padding: '8px 16px',
borderRadius: 8,
cursor: 'pointer',
border: 'none',
},
variants: {
variant: {
filled: {},
outlined: {
background: 'transparent',
border: '2px solid currentColor',
},
text: {
background: 'transparent',
},
},
color: {
primary: {},
danger: {},
},
},
compoundVariants: [
// Filled
{
variants: {
variant: 'filled',
color: 'primary',
},
style: {
background: '#d36ac2',
color: 'white',
},
},
{
variants: {
variant: 'filled',
color: 'danger',
},
style: {
background: '#dc2626',
color: 'white',
},
},
// Outlined
{
variants: {
variant: 'outlined',
color: 'primary',
},
style: {
color: '#d36ac2',
},
},
{
variants: {
variant: 'outlined',
color: 'danger',
},
style: {
color: '#dc2626',
},
},
// Text
{
variants: {
variant: 'text',
color: 'primary',
},
style: {
color: '#d36ac2',
},
},
{
variants: {
variant: 'text',
color: 'danger',
},
style: {
color: '#dc2626',
},
},
],
defaultVariants: {
variant: 'filled',
color: 'primary',
},
});The color variant has empty objects in variants because the styling only makes sense in combination with variant. Compound variants carry the actual values.
Pattern: size × density override
Sometimes a compound variant tweaks a base property only at a specific intersection.
const card = ev({
base: {
padding: 16,
borderRadius: 12,
},
variants: {
size: {
sm: {
padding: 12,
},
md: {
padding: 16,
},
lg: {
padding: 24,
},
},
density: {
comfortable: {},
compact: {},
},
},
compoundVariants: [
{
variants: {
size: 'lg',
density: 'compact',
},
style: {
padding: 16,
},
},
],
});Only the lg + compact corner overrides the size's natural padding.
Matching several values
A condition can be an array. It matches when the variant has any of the listed values, so one entry covers what would otherwise be a copy per value.
const button = ev({
variants: {
variant: {
filled: {},
outlined: {},
text: {},
},
color: {
primary: {},
danger: {},
},
},
compoundVariants: [
{
variants: {
variant: ['outlined', 'text'],
color: 'danger',
},
style: {
color: 'crimson',
},
},
],
});
button({ variant: 'outlined', color: 'danger' }); // includes the compound style
button({ variant: 'text', color: 'danger' }); // includes it too
button({ variant: 'filled', color: 'danger' }); // does NOT include itAxes are still combined with AND. Every listed axis has to match; an array only widens what counts as a match for its own axis. An empty array never matches.
Order matters
Compound variants are appended after simple variants for each call, in the order they appear in the array. If two compound variants both match and set the same property, the later one wins.
compoundVariants: [
{
variants: {
size: 'lg',
},
style: {
fontWeight: 600,
},
},
{
variants: {
size: 'lg',
color: 'primary',
},
style: {
fontWeight: 700, // overrides the entry above
},
},
],You can include compound variants that match only a single axis. They behave like normal variants but are useful when you want to keep all "conditional" rules in one place.
See also
Last updated on