variants
The .variants() method chains onto a stylesheet to add conditional styling based on component state. Variant types are declared via a generic parameter, giving you full type safety from definition through to consumption.
Signature
const styles = stylesheet({ ... }).variants<VariantMap>(($) => ({
[$.variantName('value')]: {
elementName: { /* token overrides */ },
},
}))Defining Variants
Pass a type parameter describing the variant keys and their allowed values. Required variants must be provided by the consumer; optional variants (marked with ?) are applied only when present:
const buttonStyles = stylesheet({
container: {
borderRadius: 'medium',
borderWidth: 'none',
cursor: 'pointer',
},
label: {},
}).variants<{
size: 'm' | 's' // required
variant: 'accent' | 'danger' // required
alignment?: 'icon-only' | 'icon-left' | 'icon-right' // optional
}>(($) => ({
[$.variant('accent')]: {
container: { bgColor: 'action' },
label: { textColor: 'on_action' },
},
[$.variant('danger')]: {
container: { bgColor: 'status_error' },
label: { textColor: 'on_status_error' },
},
[$.size('m')]: {
container: { paddingX: 3 },
},
[$.size('s')]: {
container: { paddingX: 2, paddingY: 1 },
},
}))The Dollar-Sign Builder
The callback receives a $ builder object with a method for each variant key. Calling $.size('m') produces a computed key that the runtime uses to match against the state passed to useStyles.
Compound Variants
Chain multiple variant calls to create compound conditions that only match when all specified variants are active simultaneously:
($ => ({
[$.size('m').alignment('icon-only')]: {
container: { paddingX: 2, paddingY: 2 },
},
[$.size('s').alignment('icon-only')]: {
container: { paddingX: 1, paddingY: 2 },
},
}))Compound variants are evaluated after individual variant rules, so they naturally override conflicting properties.
Pseudo-state Variants
You can target pseudo-states like hover by using the 'element:pseudo' key syntax inside a variant block:
[$.variant('accent')]: {
container: { bgColor: 'action' },
label: { textColor: 'on_action' },
'container:hover': {
container: { bgColor: 'action_secondary' },
label: { textColor: 'on_action_secondary' },
},
}Responsive Variants
Breakpoints work inside variant blocks, so a variant can define responsive overrides for its elements:
[$.layout('grid')]: {
container: {
style: { display: 'grid', gridTemplateColumns: '1fr' },
'@md': {
style: { gridTemplateColumns: '1fr 1fr' },
},
'@lg': {
style: { gridTemplateColumns: '1fr 1fr 1fr' },
},
},
}Named Styles ($composes)
When multiple variants share common element overrides, you can extract them into a named style and compose them into variants. This avoids duplicating the same token values across variant rules.
Defining Named Styles
Use $('name') to define a named style block. Named styles are not variant rules — they are reusable fragments that can be composed into actual variants:
const buttonStyles = stylesheet({
container: { borderRadius: 'medium' },
label: {},
}).variants<{ size: 'm' | 's'; variant: 'accent' | 'danger' }>(($) => ({
// Named style — shared across variants
$('interactive'): {
'container:hover': {
container: { shadow: 'medium' },
},
},
[$.variant('accent')]: {
$compose: 'interactive',
container: { bgColor: 'action' },
label: { textColor: 'on_action' },
},
[$.variant('danger')]: {
$compose: 'interactive',
container: { bgColor: 'status_error' },
label: { textColor: 'on_status_error' },
},
}))Both accent and danger inherit the hover shadow from interactive, without repeating it.
Element-Level $compose
Inside a single variant rule, you can compose one element from another element defined in the same block. This is useful when several elements within a variant share a base set of tokens:
[$.size('s')]: {
base: { paddingX: 2, bgColor: 'muted' },
container: {
$compose: 'base',
borderRadius: 'medium',
},
sidebar: {
$compose: 'base',
borderRadius: 'small',
},
}Here both container and sidebar inherit paddingX and bgColor from base, then add their own overrides. The composed element (base) is not included in the final output — it only serves as a source for composition.
Composing Multiple Sources
Pass an array to $compose to merge from multiple named styles. They are applied in order, and the variant's own properties always take priority:
$('borders'): {
container: { borderWidth: 'thin', borderColor: 'subtle' },
},
$('spacing'): {
container: { paddingX: 3, paddingY: 2 },
},
[$.size('m')]: {
$compose: ['borders', 'spacing'],
container: { bgColor: 'elevated' }, // own props override composed ones
}Consuming Variants
Pass variant values as the second argument to useStyles:
function Button({ label, size, variant }: Props) {
const s = useStyles(buttonStyles, { size, variant })
return (
<button {...s.container}>
<span {...s.label}>{label}</span>
</button>
)
}