Responsive and states
Mobile-first, hover/focus-visible, group/peer, aria-* and container queries.
Updated
What variants are
In Tailwind, a variant is a prefix that applies a class only under certain conditions: on a wider screen (md:), on hover (hover:), in dark mode (dark:), when the parent is in a certain state (group-hover:). The equivalent of media queries and pseudo-classes in CSS, written directly on the element.
<button class="bg-surface hover:bg-hover md:px-6 dark:border-line">| Kind of variant | Examples | The CSS equivalent |
|---|---|---|
| breakpoint | sm: md: lg: max-md: |
@media (min-width: ...) |
| state | hover: focus-visible: active: disabled: |
pseudo-classes |
| relational | group-hover: peer-checked: has-[img]: |
parent / sibling selectors |
| attributes | aria-expanded: data-[state=open]: |
attribute selectors |
| environment | dark: motion-reduce: print: |
preference media queries |
| container | @md: |
@container |
Variants can be combined: md:hover:bg-hover.
Mobile-first
A class without a prefix = every screen. The prefix = from that breakpoint up.
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4">| Prefix | Minimum width |
|---|---|
sm: |
40rem (640px) |
md: |
48rem (768px) |
lg: |
64rem (1024px) |
xl: |
80rem (1280px) |
Think of it this way: you write the mobile version, then add what changes on big screens. max-md: = only below md.
States
<a class="text-muted hover:text-fg focus-visible:outline-2 active:scale-95">
<input class="border invalid:border-red-500 disabled:opacity-50 placeholder:text-gray-400" />
<li class="odd:bg-gray-50 first:pt-0 last:border-0">focus-visible: instead of focus: — the ring shows up only for the keyboard, not on click.
group and peer
You style a child by the state of its parent (group) or of a sibling (peer):
<a class="group">
<h3 class="group-hover:text-blue-600">Title</h3>
</a>
<input class="peer" type="checkbox" />
<span class="peer-checked:line-through">Task</span>ARIA and data-* attributes
<button aria-expanded="true" class="aria-expanded:rotate-180">
<div data-state="open" class="data-[state=open]:block hidden">Perfect with headless components (Radix, React Aria) that set these attributes.
Container queries
Responsive by the parent's width, not the screen's — ideal for cards reused in different places:
<div class="@container">
<div class="flex flex-col @md:flex-row">...</div>
</div>Summary
- A variant = a conditional prefix; no prefix = every screen.
- Mobile-first: write for mobile, add
md:,lg:for big screens. focus-visible:for focus;group-*/peer-*for relationships;@containerfor components.