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>
PrefixMinimum widthTypical device
(none)0phone
sm:40rem / 640pxlarge phone, small tablet
md:48rem / 768pxtablet
lg:64rem / 1024pxlaptop
xl:80rem / 1280pxdesktop
2xl:96rem / 1536pxwide desktop
  • Every breakpoint is a min-width query, 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 later xl: 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, and min-[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

VariantApplies 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.

Utility-first basics Configuration and theming

Last refreshed 2026-09-18.