What Tailwind variant prefixes do
Tailwind utility classes are unconditional by default — bg-blue-500 always
applies. Variant prefixes make a utility conditional: hover:bg-blue-500 only
paints on hover, md:bg-blue-500 only above the medium breakpoint, and
dark:bg-blue-500 only in dark mode. This reference lists the common variants,
the CSS selector or at-rule each compiles to, and how to stack them.
How Tailwind compiles variants
A variant is a prefix plus a colon. At build time, Tailwind’s JIT engine reads the class name, looks up the variant in its variant registry, and wraps the utility’s CSS declaration in the matching selector or at-rule:
/* hover:bg-blue-500 */
.hover\:bg-blue-500:hover { background-color: #3b82f6 }
/* md:flex */
@media (min-width: 768px) { .md\:flex { display: flex } }
/* dark:text-white (media strategy) */
@media (prefers-color-scheme: dark) { .dark\:text-white { color: #fff } }
/* focus-visible:ring-2 */
.focus-visible\:ring-2:focus-visible { --tw-ring-offset-shadow: ...; }
The backslash escaping in class names is automatic — you write the class with colons in HTML and Tailwind handles the CSS escaping.
Variant categories
State variants target pseudo-classes on the element itself:
| Variant | Compiles to |
|---|---|
hover: | :hover |
focus: | :focus |
focus-visible: | :focus-visible |
focus-within: | :focus-within |
active: | :active |
disabled: | :disabled |
checked: | :checked |
placeholder: | ::placeholder |
first: | :first-child |
last: | :last-child |
odd: / even: | :nth-child(odd/even) |
Responsive variants wrap in min-width media queries (mobile-first):
| Variant | Breakpoint |
|---|---|
sm: | >= 640px |
md: | >= 768px |
lg: | >= 1024px |
xl: | >= 1280px |
2xl: | >= 1536px |
Theme variants:
| Variant | Effect |
|---|---|
dark: | @media (prefers-color-scheme: dark) or .dark class selector, depending on darkMode config |
print: | @media print |
motion-safe: | @media (prefers-reduced-motion: no-preference) |
motion-reduce: | @media (prefers-reduced-motion: reduce) |
Group and peer variants for parent/sibling-driven styling:
| Variant | What it needs | Compiles to |
|---|---|---|
group-hover: | group class on ancestor | .group:hover .element |
group-focus: | group class on ancestor | .group:focus .element |
peer-checked: | peer class on earlier sibling | .peer:checked ~ .element |
peer-focus: | peer class on earlier sibling | .peer:focus ~ .element |
Attribute variants for DOM state without additional JavaScript:
| Variant | Targets |
|---|---|
aria-expanded: | [aria-expanded="true"] |
aria-selected: | [aria-selected="true"] |
data-[state=open]: | [data-state="open"] arbitrary data attribute |
Stacking variants
Variants combine into a single condition when stacked. md:dark:hover:underline underlines only when all three hold simultaneously: viewport ≥ 768px, dark mode active, and element hovered.
Write stacked variants outer to inner: responsive → theme → state is the conventional order, e.g. lg:dark:hover:bg-brand-700. Tailwind does not enforce order for the generated CSS, but consistency in the codebase makes scanning class lists much easier.
Arbitrary variants
When no named variant covers your need, use square brackets to write any selector directly:
<li class="[&:nth-child(3)]:font-bold">
<div class="supports-[display:grid]:grid">
<p class="[@media(min-width:900px)]:text-xl">
The & represents the element. Use arbitrary variants sparingly — common patterns should use config extensions rather than one-off bracket syntax scattered across templates.