Hover, Focus, and State Variants
Styling interactive states like hover, focus, and disabled with variant prefixes instead of pseudo-classes.
阅读需 2 分钟
Just like responsive breakpoints, interactive states in Tailwind are expressed as prefixes on ordinary utility classes rather than separate :hover or :focus rules in a stylesheet.
<button class="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 active:bg-blue-800">
Save
</button>hover:bg-blue-700 only applies while the button is hovered; active:bg-blue-800 only while it's being clicked. Both compile to normal CSS pseudo-classes (.hover\:bg-blue-700:hover { background-color: ...; }) — the prefix is purely a naming convention that lets the state live right next to the base style it modifies, instead of in a separate rule you'd have to go find.
Focus states matter for accessibility, not just polish
<input
type="email"
class="rounded-md border border-slate-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200"
/>Every interactive element needs a visible focus state for keyboard users — removing the browser's default outline (focus:outline-none) without replacing it with something equally visible is a common accessibility mistake. Here, focus:outline-none is paired with a focus:ring-2 so the input never loses a visible focus indicator, it's just restyled to match the design instead of using the default blue browser outline.
Disabled state
<button
disabled
class="rounded-md bg-blue-600 px-4 py-2 text-white disabled:cursor-not-allowed disabled:opacity-50"
>
Submit
</button>disabled: matches the :disabled pseudo-class, which only applies to elements that actually support the disabled HTML attribute (<button>, <input>, <select>, <textarea>, <fieldset>). Fading the button (opacity-50) and swapping the cursor communicates "this can't be clicked right now" without any JavaScript-driven class toggling — the browser's own state drives the style.
Group and peer: styling based on a relative's state
Sometimes you want to style an element based on the state of a different element — hovering a card should highlight its title, or a checked checkbox should restyle its label. Tailwind handles this with group and peer:
<div class="group rounded-lg border p-4 hover:border-blue-400">
<h3 class="font-semibold text-slate-800 group-hover:text-blue-600">Project Alpha</h3>
<p class="text-sm text-slate-500">Updated 2 days ago</p>
</div>Marking the parent group lets any descendant use group-hover: to react to the parent's hover state, not its own. Without this, you'd need JavaScript to add a class to the title whenever the card is hovered. peer works the same way but for sibling elements (an input's peer-invalid: state styling its adjacent error message, for example) rather than a parent-child relationship.
Stacking variants
Prefixes combine freely, including with the responsive breakpoints from the previous lesson:
<button class="bg-blue-600 hover:bg-blue-700 md:hover:bg-blue-800">Reading right to left within the prefix chain: at md and up, when hovered, use this darker shade. Because variants are just prefixes rather than a fixed set of "responsive" or "state" utilities, any combination you can describe in English is expressible directly in the class list.