Skip to content

Variants & Modifiers

Variants in CSSzyx use nested objects. The key is the variant name; the value is another sz object with the styles to apply.

<button sz={{
bg: 'blue-500',
hover: { bg: 'blue-600' },
focus: { outline: 'none', ring: 2 },
active: { opacity: 80 },
disabled: { opacity: 50, cursor: 'not-allowed' },
visited: { color: 'purple-600' },
}} />

State modifiers: hover, focus, active, disabled, visited, target, first, last, odd, even, only, empty.

Camel-cased aliases: focusWithin, focusVisible, firstOfType.

Breakpoints use a mobile-first approach — unprefixed props apply to all screen sizes, nested props apply at the given breakpoint and up:

<div sz={{
w: 'full', // mobile: full width
md: { w: '1/2' }, // ≥768px: half width
lg: { w: '1/3' }, // ≥1024px: one third
xl: { flex: true }, // ≥1280px: flex layout
}} />

Available breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px), 2xl (1536px).

<div sz={{
md: {
maxLg: { hidden: true }, // md:max-lg:hidden (768–1023px range)
},
}} />
<div sz={{
min: { '[320px]': { textAlign: 'center' } }, // min-[320px]:text-center
max: { '[600px]': { bg: 'sky-300' } }, // max-[600px]:bg-sky-300
}} />
<div sz={{
bg: 'white',
color: 'gray-900',
dark: {
bg: 'gray-800',
color: 'white',
},
}} />
<div sz={{ '@container': true }}>
<div sz={{
'@md': { flex: true },
'@lg': { gridCols: 3 },
}} />
</div>
<p sz={{
before: { content: "'»'", mr: 1 },
after: { content: "'«'", ml: 1 },
placeholder: { color: 'gray-400' },
selection: { bg: 'blue-200' },
firstLine: { uppercase: true },
firstLetter: { text: '5xl', fontWeight: 'bold', float: 'left', mr: 2 },
}} />

Add group class to the parent, then use { group: { hover: { ... } } } on children:

<div sz={{ group: true }}>
<p sz={{
group: {
hover: { color: 'blue-500' },
},
}}>
Turns blue when the parent is hovered.
</p>
</div>

Add peer class to the sibling, then use { peer: { ... } } on the element to style:

<input sz={{ peer: true }} type="checkbox" />
<p sz={{
peer: {
checked: { color: 'green-500' },
},
}}>
Turns green when the checkbox is checked.
</p>
<div sz={{ group: 'sidebar' }}>
<div sz={{
group: {
sidebar: {
hover: { bg: 'gray-100' },
},
},
}} />
</div>

Style children based on a data-* attribute on the parent group element:

<div sz={{ group: true }} data-active>
<p sz={{
group: {
data: {
active: { color: 'blue-600' }, // group-data-[active]:text-blue-600
'state=open': { bg: 'green-50' }, // group-data-[state=open]:bg-green-50
},
},
}}>
Blue when parent has [data-active]. Green when parent has [data-state="open"].
</p>
</div>

Named group variant (combines group name + data attribute):

<div sz={{ group: 'card' }} data-active>
<p sz={{
group: {
card: {
data: { active: { color: 'blue-600' } }, // group-data-[active]/card:text-blue-600
},
},
}} />
</div>

Style children based on an aria-* attribute on the parent group element:

<details sz={{ group: true }}>
<summary sz={{
group: {
aria: {
expanded: { color: 'blue-600' }, // group-aria-expanded:text-blue-600
'current=page': { fontWeight: 'bold' }, // group-aria-[current=page]:font-bold
},
},
}} />
</details>

Standard ARIA states (checked, disabled, expanded, hidden, pressed, required, selected) emit the bare form (group-aria-expanded:). Non-standard or value-match attributes emit bracket form (group-aria-[current=page]:).

<input sz={{ peer: true }} data-error />
<p sz={{
peer: {
data: {
error: { color: 'red-600' }, // peer-data-[error]:text-red-600
},
},
}} />
<input sz={{ peer: true }} aria-invalid="true" />
<p sz={{
peer: {
aria: {
'invalid=true': { color: 'red-600' }, // peer-aria-[invalid=true]:text-red-600
},
},
}} />
<div sz={{
data: {
active: { bg: 'blue-50', borderColor: 'blue-200' },
error: { bg: 'red-50', borderColor: 'red-200' },
},
}} />
<button sz={{
aria: {
expanded: { bg: 'blue-100' },
disabled: { opacity: 50 },
},
}} />
<form sz={{
has: {
'[data-error]': { borderColor: 'red-500' },
checked: { bg: 'green-50' },
},
}} />
<li sz={{
not: {
first: { mt: 4 },
last: { mb: 4 },
},
}} />
<div sz={{
motionReduce: { transition: 'none' },
print: { hidden: true },
portrait: { flex: true },
forcedColors: { border: 2 },
}} />

For selectors not covered by the built-in variants:

<div sz={{
'[&>span]': { color: 'blue-500' },
'[&_*]': { p: 4 },
'*': { boxSizing: 'border' },
}} />

Variants can be nested arbitrarily deep:

<div sz={{
md: {
hover: {
bg: 'blue-500', // md:hover:bg-blue-500
},
},
dark: {
hover: {
color: 'white', // dark:hover:text-white
},
},
}} />