Responsive and interactive states
Coordinate state with group and peer
Style one element based on the state of a parent or sibling using group and peer variants while keeping the HTML relationship clear.
Sometimes an element needs to change based on the state of a different element. Tailwind gives you two variants for that: group for ancestors and peer for siblings.
group: react to a parent
Mark the state owner with group, then use group-hover: on a descendant:
<a class="group" href="/docs">
Docs <span class="motion-safe:group-hover:translate-x-1">→</span>
</a>
Hover the link and the arrow slides right. The <a> is still the real link. group just gives descendants a way to see its state. In the generated CSS this is a plain descendant selector: the .group ancestor in its hover state, then the span.
The motion-safe: prefix means the arrow only moves for visitors who haven’t asked for reduced motion.
peer: react to a sibling
peer lets a later sibling respond to a control. Here’s an inline validation message:
<label>
<span>Email</span>
<input class="peer" type="email" required>
<span class="hidden text-red-700 peer-invalid:block">
Enter a valid email address.
</span>
</label>
Type hello and the message appears, because the input matches :invalid. Type [email protected] and it disappears. No JavaScript.
Peer variants use the CSS sibling combinator, so the styled element must come after the peer in the DOM. If your message needs to sit above the input visually, don’t reorder meaningful content to please the selector. Use Grid or Flexbox order, or a different state mechanism.
Prefer native state
checked, invalid, disabled, focus: these states already exist and already talk to the platform. Use them when you can. For application state like an open menu, put it in an attribute such as aria-expanded and style from that with aria-expanded:. Appearance and accessibility then share one source of truth.
Naming nested groups
When groups nest, an inner group-hover: may respond to the outer group. Name them:
<article class="group/card">
<a class="group/link" href="/docs">
<span class="group-hover/card:underline group-hover/link:text-blue-700">Docs</span>
</a>
</article>
group-hover/card: only responds to the card. The name says which state owner is in charge.
CSS is not the whole validation
Showing a message with peer-invalid: is a start, not the finish. Connect it to the input with aria-describedby, decide when validation should appear (often after the first blur, not on the first keystroke), and make dynamic updates understandable to assistive technology.
Try this: build one grouped link and one peer-validated field. Add a nested named group, test keyboard focus and an invalid submission, and write out the exact ancestor or sibling selector each variant generates.
Lesson completed