Responsive and state variants
Mobile-first breakpoints, the state variants that replace handwritten interaction CSS, and how group, peer and custom variants compose.
Mobile first, then min-width
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
...
</div>
<!-- one column stack on phones, two from 768px, four from 1280px -->
<article class="col-span-1 md:col-span-2 xl:col-span-4">...</article>| Prefix | Minimum width | Typical device |
|---|---|---|
| (none) | 0 | phone |
sm: | 40rem / 640px | large phone, small tablet |
md: | 48rem / 768px | tablet |
lg: | 64rem / 1024px | laptop |
xl: | 80rem / 1280px | desktop |
2xl: | 96rem / 1536px | wide desktop |
- Every breakpoint is a
min-widthquery, so write the small-screen layout unprefixed and add prefixes only where the layout has to change. - Styles cascade upward: a
lg:utility is overridden by a laterxl:utility for the same property, and the source order of the generated CSS decides ties. - Range variants exist too:
max-md:applies below a breakpoint, andmin-[1024px]:uses an arbitrary width.
State variants
<a class="text-slate-600 transition-colors hover:text-slate-900
focus-visible:outline-2 focus-visible:outline-offset-2
aria-current:font-semibold aria-current:text-slate-900">Docs</a>
<ul class="group">
<li class="group-hover:bg-slate-50 group-focus-within:bg-slate-50">Item</li>
</ul>
<div class="rtl:mr-4 rtl:ml-0 motion-reduce:transition-none">...</div>💡
Prefer
focus-visible: over focus: for interactive elements. It styles keyboard focus without drawing a ring on every mouse click, and it keeps the focus indicator that keyboard users depend on.Composing variants
| Variant | Applies when |
|---|---|
hover:, active:, focus: | the user interacts with the element |
focus-visible: | focus came from the keyboard |
disabled:, checked:, required: | the form control is in that state |
first:, last:, odd:, even: | the element holds that position among siblings |
group-hover: | an ancestor marked group is hovered |
peer-checked: | a sibling marked peer is checked |
dark: | the colour scheme resolves to dark |
aria-expanded:, data-open: | the matching attribute is present |
*:, **: | direct children, or all descendants |
motion-safe:, print:, rtl: | the capability or media query matches |
<!-- name a group when they nest, so the outer one is not triggered -->
<div class="group/menu">
<button class="group-hover/menu:text-indigo-600">Menu</button>
</div>
<!-- target siblings with peer; order in the DOM matters -->
<input id="ok" type="checkbox" class="peer sr-only">
<label for="ok" class="peer-checked:bg-emerald-600 peer-checked:text-white">Accept</label>
<!-- stack variants: on wide screens and only while hovered -->
<article class="lg:hover:shadow-lg">...</article>FAQ
Why does my custom colour not work with a variant prefix?
Variants only wrap utilities that exist. If a class was never generated, because it was built at runtime or the value is missing from the theme, no prefix can bring it back.
How do I add a variant Tailwind does not ship?
In v4 declare it in CSS with
@custom-variant, for example @custom-variant hocus (&:hover, &:focus); which you can then write as hocus:. In v3 the equivalent is the addVariant plugin API.Related
Utility-first basics Configuration and theming
Last refreshed 2026-09-18.