Tailwind Variants & Responsive Design
In Tailwind, a utility like bg-white applies unconditionally. Variants are prefixes that apply it only under a condition: at a breakpoint (md:grid-cols-3), on hover (hover:bg-gray-50), when focused by keyboard (focus-visible:ring-2), when a parent is hovered (group-hover:opacity-100), when a sibling input is invalid (peer-invalid:visible), when an element has a data attribute (data-[state=open]:rotate-180), in dark mode, or inside a container of a certain size (@md:flex-row).
Variants replace most hand-written media queries, pseudo-classes, and state selectors. They stack (md:hover:dark:bg-gray-800), and they compose with component libraries that expose state through attributes, which is how headless UI libraries and Tailwind work so well together.
TL;DR
- Breakpoints are mobile-first: unprefixed utilities apply to all sizes, and
md:applies from themdwidth up. - State variants:
hover:,focus:,focus-visible:,active:,disabled:,invalid:,checked:,placeholder:,first:,odd:… - **
group-*styles children based on a parent's state;peer-*** styles siblings based on a previous sibling's state. - **
data-*andaria-*variants style from attributes;has-*andnot-*** use relational selectors. - Container queries:
@containeron a parent, and@sm:,@lg:… on children. - Variants stack, and arbitrary variants (
[&>li]:py-2) cover anything else.
Quick Example
Core Concepts
Responsive Breakpoints
Default breakpoints (v4): sm 40rem, md 48rem, lg 64rem, xl 80rem, 2xl 96rem. They're min-width queries, so design for small screens first, then add md: and lg: overrides. Target ranges with max-* variants (md:max-lg:hidden means only between md and lg) and custom one-offs with min-[900px]:. Customize via --breakpoint-* in the theme.
State Variants
Prefer focus-visible: for focus rings, so keyboard users see them and mouse clicks don't trigger noisy outlines. Never remove focus indication without a replacement. See accessibility.
group and peer
- Mark a parent with
group, then style descendants withgroup-hover:,group-focus-within:,group-data-[active]:, and so on. Named groups (group/itemwithgroup-hover/item:) handle nested groups. - Mark an element with
peer, then style later siblings withpeer-checked:,peer-invalid:,peer-focus:. It's great for custom checkboxes, floating labels, and validation messages without JavaScript.
Attribute Variants
- **
aria-***:aria-expanded:rotate-180,aria-selected:bg-brand-50,aria-disabled:opacity-50. Styling from ARIA state keeps visuals and accessibility in sync. - **
data-***:data-[state=open]:…,data-active:…. Headless UI libraries (Radix, Headless UI, React Aria) expose component state through data attributes, so Tailwind styles every state declaratively.
Relational and Negation Variants
- **
has-***:has-[:checked]:border-brand-500styles a card when it contains a checked input.group-has-*andpeer-has-*exist too. - **
not-***:not-last:border-b,not-disabled:hover:bg-gray-100. - **
in-***: style based on any ancestor's state without naming a group.
Container Queries
Add @container (optionally named: @container/sidebar) to a parent, and use @sm:, @md:, @lg: (and @max-md:) on descendants to respond to the container's width instead of the viewport's. It's built into v4. See container queries.
Media and Preference Variants
dark:, motion-reduce:/motion-safe:, contrast-more:, forced-colors:, print:, portrait:/landscape:, pointer-coarse:, and supports-[display:grid]:. Respect user preferences, for example by disabling non-essential animation under motion-reduce:.
Stacking and Arbitrary Variants
Variants chain left to right: md:hover:bg-gray-50, dark:group-hover:text-white. For anything not built in, use arbitrary variants: [&>li]:py-2 (style direct child lis), [&:nth-child(3)]:font-bold, [@supports(backdrop-filter:blur(0))]:bg-white/60. For reused ones, define a custom variant with @custom-variant.
Best Practices
Design Mobile-First
Write base utilities for the smallest screen, then layer larger breakpoints. It keeps class lists predictable, and avoids fighting max-width overrides.
Use Container Queries for Reusable Components
Components that appear in sidebars, grids, and main columns should respond to their container, not the viewport. Reserve viewport breakpoints for page-level layout.
Style State From Attributes
Drive visual states from aria-* and data-* attributes rather than toggling many classes in JavaScript. The DOM becomes the single source of truth, and accessibility attributes are guaranteed to match visuals.
Keep Interactions Accessible
Pair hover: with focus-visible: equivalents, ensure touch users can access hover-revealed content, and respect motion-reduce.
Common Mistakes
Thinking Breakpoints Are Max-Width
Use sm:max-md:block for a single range.
Using peer on a Later Sibling
peer-* only styles elements that come after the peer in the DOM (CSS sibling combinators look forward). Reorder markup, use has-* on a common parent, or use a group.
Removing Focus Styles
focus:outline-none without a focus-visible:ring-* replacement makes keyboard navigation invisible, which is a common accessibility failure.
FAQ
Are Tailwind breakpoints min-width or max-width?
Min-width. md:flex applies from the md breakpoint and up. Unprefixed utilities apply at all sizes. For upper bounds or ranges, use max-* variants like max-md: or combined md:max-lg:.
What's the difference between group and peer?
group styles descendants of an element based on the element's state (hovering a card changes its children). peer styles subsequent siblings based on a sibling's state (an invalid input shows the error message after it).
How do I use container queries in Tailwind?
Add the @container class to a parent, then use container variants like @md:grid-cols-2 on its children. They respond to the parent's inline size rather than the viewport. Name containers (@container/card, @md/card:) when nesting.
How do I style based on a data attribute?
Use data-* variants: data-[state=open]:bg-gray-100, or data-active: for boolean attributes. It pairs naturally with headless component libraries that expose state through data attributes.
Related Topics
- Tailwind — The framework overview
- Tailwind Theme Configuration — Custom breakpoints and variants
- Tailwind Dark Mode — The dark: variant and theming
- Responsive Design — Layouts across devices
- CSS Container Queries — The CSS feature behind @container
- Accessibility — Focus, motion, and ARIA states