Overview

Tailwind variants are prefixes that scope a utility to a CSS condition. hover:bg-brand means bg-brand only when the element is hovered. v4 ships group-*, peer-*, has-*, supports-*, and data-* as first-class variants, plus an escape hatch for any CSS selector or at-rule. The parent reference is tailwind.

Use group-* to style children based on parent state

Mark the parent with group and style descendants with group-hover:, group-focus:, group-aria-expanded:, or any other state.

<div class="group rounded-card p-4 hover:shadow-card">
  <h2 class="text-ink group-hover:text-brand transition-colors">Card title</h2>
  <p class="text-ink/60 group-hover:text-ink/80">Description</p>
</div>

Name groups when nesting: group/outer and group/inner. Use group-hover/outer: on a deeply nested child to target the specific ancestor.

<div class="group/card">
  <div class="group/section">
    <span class="group-hover/card:text-brand group-hover/section:underline">
      Nested label
    </span>
  </div>
</div>

Use peer-* to style siblings based on sibling state

peer marks an element as the source; peer-checked:, peer-focus:, and peer-invalid: style a following sibling.

<input id="toggle" type="checkbox" class="peer sr-only" />
<label for="toggle" class="cursor-pointer peer-checked:text-brand">
  Enable notifications
</label>

Peers must precede their targets in the DOM. A peer-* variant on an element that precedes the peer source has no effect. Name peers the same way you name groups: peer/input and peer-focus/input:.

Use has-* to style parents based on child state

has-* is the CSS :has() selector wrapped as a variant. Style a container when it contains a specific element or state.

<div class="has-[input:focus]:ring-2 has-[input:focus]:ring-brand rounded-md">
  <input type="text" placeholder="Search" />
</div>

The outer div gains a ring when any input inside it is focused. No JavaScript state management needed. For the deeper CSS reference, see css-has-selector.

Combine with other variants: has-[.error]:border-danger styles a field group red when it contains an error message.

Use supports-* to progressively enhance for modern features

supports-* maps to CSS @supports. Apply a utility only when the browser supports a given property or value.

<div class="grid supports-[container-type:inline-size]:container-query-layout">

Use supports-* sparingly. Most modern CSS features have shipped in every evergreen browser. Reach for it when you need a meaningful fallback for a genuinely experimental feature like scroll-driven animations or @starting-style.

Use data-* to style based on custom data attributes

data-* variants match data-* attributes on the element.

<button data-variant="primary" class="data-[variant=primary]:bg-brand data-[variant=primary]:text-white">
  Submit
</button>

Pair with JavaScript that sets data-state="open" / data-state="closed" (the pattern Radix UI and similar libraries use) to drive visible state from semantic attributes instead of className toggling.

<div data-state="open" class="hidden data-[state=open]:block">Panel content</div>

Write arbitrary variants for one-off CSS selectors

Use [...] to write an arbitrary CSS selector or at-rule as a variant when no built-in variant fits.

<p class="[&:nth-child(3)]:font-bold [&>strong]:text-brand">

The & is the placeholder for the element itself, matching CSS nesting syntax. Use this for pseudo-classes that Tailwind does not expose as named variants (nth-child, not, is), for attribute selectors, or for scoped overrides inside a third-party component.

Order variants consistently; stack them left to right

Multiple variants stack left to right in class name order. The rightmost element is the base utility.

<button class="sm:hover:dark:bg-brand-900">

This reads: on small-and-up viewports, when hovered, in dark mode, use bg-brand-900. The Prettier plugin for Tailwind sorts variants in a canonical order that matches this mental model. Install it; do not debate order in review. See tailwind-responsive for breakpoint variant ordering.

Prefer semantic variants over JavaScript state syncing

Replace boolean class toggling in JavaScript with data attributes and data-* variants wherever possible. The result is fewer state variables, fewer re-renders in react, and a component whose visual states are readable from the HTML alone.