Getting started
Variants
A variant is a prefix that applies a utility under a condition: hover:bg-primary/90, md:flex-row, dark:bg-slate-800. Monsoon predeclares 10,237 variant classes, generated for the utility groups where each condition is useful.
How variants work
Write the variant, a colon and the utility. The variant only changes when the rule applies, never what it does:
<button class="bg-primary text-primary-foreground hover:bg-primary/90 active:scale-95 focus-visible:ring-2 disabled:opacity-50">
Save
</button>Because the stylesheet is prebuilt, a variant class exists only where it was generated. hover:bg-primary exists, hover:p-4 does not. The class reference lists the variants of every class, and npx monsooncss classes --filter hover: lists them in a terminal.
Every variant
| Variant | Applies when | CSS |
|---|---|---|
hover: | the pointer is over the element | :hover |
focus: | the element has focus | :focus |
focus-visible: | the element has keyboard focus | :focus-visible |
focus-within: | the element or a descendant has focus | :focus-within |
active: | the element is being pressed | :active |
disabled: | a form control is disabled | :disabled |
group-hover: | an ancestor with the group class is hovered | .group:hover .x |
peer-checked: | an earlier sibling with the peer class is checked | .peer:checked ~ .x |
first:, last: | the element is the first or last child | :first-child, :last-child |
odd:, even: | the element is an odd or even child | :nth-child(odd), :nth-child(even) |
motion-safe: | the visitor has not asked for reduced motion | @media (prefers-reduced-motion: no-preference) |
motion-reduce: | the visitor asked for reduced motion | @media (prefers-reduced-motion: reduce) |
dark: | the nearest themed ancestor is dark, or none is set and the system is dark | selectors, @scope and prefers-color-scheme |
sm: to 2xl: | the window is at least 640, 768, 1024, 1280 or 1536px wide | @media (min-width: ...) |
@xs: to @xl: | the nearest container is at least 20, 24, 28, 32 or 36rem wide | @container (min-width: ...) |
Details on the last three: Dark mode, Responsive design and Container queries.
Which utilities get which variants
Each variant is generated for a curated set of utility groups. This table is computed from the class index that ships with this release:
| Variant | Typical use | Classes per category |
|---|---|---|
hover: | semantic colors, white, black, palette steps 500 to 700, opacity modifiers, shadows, opacity, transforms, underline | colors 574, transforms 113, effects 26, typography 2 |
focus: | semantic colors, rings, outlines, sr-only | colors 137, borders 25, accessibility 2 |
focus-visible: | the same as focus: | colors 137, borders 25, accessibility 2 |
focus-within: | semantic colors, rings, outlines | colors 137, borders 25 |
active: | semantic colors, white, black, opacity modifiers, shadows, opacity, transforms, underline | colors 301, transforms 113, effects 26, typography 2 |
disabled: | semantic colors, opacity, cursor-not-allowed, pointer-events-none | colors 69, effects 16, interactivity 3 |
group-hover: | colors, display, visibility, shadows, opacity, transforms | colors 342, transforms 113, effects 26, layout 16, typography 2 |
peer-checked: | semantic colors, display, visibility, shadows, opacity, transforms | transforms 113, colors 69, effects 26, layout 16, typography 2 |
first: last: odd: even: | semantic backgrounds and borders, light neutral backgrounds, zero padding, margin and border widths | colors 56, spacing 14, borders 5 |
dark: | palette colors, white, black, opacity modifiers | colors 593 |
motion-safe: motion-reduce: | transitions and animations | transitions 7, animation 5 |
sm: to 2xl: | display, position, flex, grid, spacing, alignment, sizing, text | spacing 449, sizing 94, flexbox 39, typography 28, grid 27, layout 19, alignment 19, accessibility 2 |
@xs: to @xl: | display, flex direction, grid, gap, padding, alignment, width, text | spacing 108, grid 25, typography 16, sizing 14, alignment 13, layout 7, flexbox 4 |
The counts are per variant: odd: has the same classes as first:, and every breakpoint has the same classes as md:.
State variants in practice
Buttons and inputs combine several state variants. transition smooths the change, and the focus ring appears only for keyboard focus:
<button class="rounded-lg bg-primary text-primary-foreground px-4 py-2 font-medium transition hover:bg-primary/90 active:scale-95 focus-visible:ring-2 focus-visible:ring-offset-2">Save</button>
<button class="rounded-lg border bg-surface px-4 py-2 font-medium transition hover:bg-surface-2 active:scale-95">Cancel</button>
<button disabled class="rounded-lg bg-primary text-primary-foreground px-4 py-2 font-medium disabled:opacity-50 disabled:cursor-not-allowed">Disabled</button>
<div class="flex items-center gap-2 rounded-lg border bg-surface px-3 py-2 focus-within:border-primary focus-within:ring-2">
<input class="flex-1 bg-transparent text-sm outline-none" placeholder="Search" aria-label="Search">
</div>Group and peer
group-hover: styles a child when an ancestor is hovered. Mark the ancestor with group, and the whole card reacts as one target:
<div class="group max-w-sm cursor-pointer rounded-xl border bg-surface p-5 transition hover:shadow-lg">
<p class="font-semibold group-hover:text-primary">Read the theming guide</p>
<p class="text-sm text-muted-foreground">Hover anywhere on the card.</p>
<span class="mt-3 inline-block text-sm font-medium text-primary transition-transform group-hover:translate-x-1">Open</span>
</div>Read the theming guide
Hover anywhere on the card.
Openpeer-checked: styles an element when an earlier sibling with the peer class is checked. It is how you build reveal panels and custom choices without JavaScript:
<label class="flex flex-wrap items-center gap-3 text-sm">
<input type="checkbox" class="peer size-4 accent-primary">
<span class="font-medium peer-checked:text-primary">Add a gift note</span>
<span class="hidden w-full rounded-lg bg-muted p-3 text-muted-foreground peer-checked:block">
The note is printed on the packing slip.
</span>
</label>The peer must come before the element it controls and share its parent, because CSS can only look at earlier siblings. Keep the input visible or give it a visible focus style, so keyboard users can see where they are.
Structural and motion variants
Structural variants style children by position, which keeps repeated markup identical. Motion variants respect the reduced motion setting of the visitor:
<ul class="overflow-hidden rounded-xl border bg-surface text-sm">
<li class="border-t first:border-t-0 odd:bg-surface-2 px-4 py-2">Invoice 1041</li>
<li class="border-t first:border-t-0 odd:bg-surface-2 px-4 py-2">Invoice 1042</li>
<li class="border-t first:border-t-0 odd:bg-surface-2 px-4 py-2">Invoice 1043</li>
</ul>
<span class="size-3 rounded-full bg-success motion-safe:animate-pulse"></span>- Invoice 1041
- Invoice 1042
- Invoice 1043
- Invoice 1044
What is not supported, and what to do instead
A prebuilt file cannot contain every combination, so a few things are deliberately missing:
| Not available | Example | Do this instead |
|---|---|---|
| Stacked variants | md:hover:bg-muted, dark:hover:bg-slate-700 | Split the conditions across two elements, rely on semantic colors that already adapt to dark mode, or write the rule in CSS. |
| A variant the class does not have | hover:p-4, md:bg-primary | Check the class reference; otherwise write a small rule. |
| Opacity modifiers on palette colors | bg-rose-500/50 | Use a lighter step, or color-mix() with var(--color-rose-500). |
| The important modifier | !p-0 | Not needed: the reset is in a cascade layer and utilities come last. |
Your own CSS can use the theme, so a missing combination takes a few lines and stays correct in every theme and scheme:
/* md:hover:bg-surface-2, written by hand */
@media (min-width: 768px) {
.nav-link:hover {
background-color: var(--surface-2);
}
}
/* a palette color at 50 percent */
.highlight {
background-color: color-mix(in oklab, var(--color-rose-500) 50%, transparent);
}Splitting conditions across elements often reads better than a stacked class anyway. Put the breakpoint on a wrapper and the state on the child: <div class="hidden md:block"> around <a class="hover:bg-muted"> gives you a link that only exists from 768px and reacts to hover.
Print, screen, direction and forced colors
Five more prefixes cover the media and the writing direction a page is shown in. Like the others they are generated only where they are useful, which the table spells out.
| Prefix | Applies | Classes | Generated for |
|---|---|---|---|
print: | When the page is printed (@media print). | 677 | Layout utilities, the same set as the breakpoints: display, position, flex and grid, spacing, sizing and text size. |
screen: | Only on screens (@media screen). | 677 | The same set as print:. |
rtl: | Inside an element with dir="rtl", or the element itself. | 494 | Utilities that name a physical side: left and right margins and padding, positions, rounded corners, borders, translate, space between, flex row and transform origin. |
ltr: | Inside an element with dir="ltr". | 494 | The same set as rtl:. |
forced-colors: | When a high contrast theme is active (@media (forced-colors: active)). | 198 | Display, borders, outlines, underlines, screen reader helpers and forced-color-adjust. |
<!-- hide the navigation and show the URL when printing -->
<nav class="flex print:hidden">...</nav>
<p class="hidden print:block">monsoon.example/report</p>
<!-- mirror a physical margin in right to left languages -->
<div dir="rtl">
<span class="ml-4 rtl:ml-0 rtl:mr-4">Badge</span>
</div>
<!-- keep a visible border when a high contrast theme removes backgrounds -->
<button class="rounded-lg bg-primary text-primary-foreground forced-colors:border">Save</button>Prefer logical utilities such as ms-4, ps-4 and border-s-2: they follow the writing direction on their own, so rtl: is only needed for the physical ones.
Frequently asked questions
Why can I not combine two variants like md:hover:?
Every combination would multiply the size of the prebuilt file. Use two elements, semantic colors, or a few lines of CSS with the theme variables.
How do I know if a variant exists for a class?
Search the class reference, which lists the variants of every class, or run npx monsooncss classes --filter with the class name.
Do variants work with component classes?
No. Variants are generated for utilities only. Components handle their own states, such as hover and focus, internally.