Static pages feel dead; interfaces feel alive because they respond to the user. Tailwind CSS styles interactive states the same way it handles breakpoints: with prefixes. hover:, focus:, active:, disabled:, and friends can be attached to any utility. In this lesson you will learn the state variants you need for buttons, forms, lists, and cards.
Prefix a utility with hover: and it applies while the pointer is over the element; active: applies while it is being pressed:
<button class="rounded-lg bg-blue-600 px-5 py-2 text-white transition
hover:bg-blue-700 active:bg-blue-800 active:scale-95">
Click me
</button>Adding the plain transition utility makes state changes animate smoothly instead of snapping. The active:scale-95 gives a satisfying press-down effect.
Keyboard users rely on visible focus indicators, so never remove them without a replacement. Tailwind gives you three related variants:
focus: — element has focus by any meansfocus-visible: — focused via keyboard (the one you usually want)focus-within: — an ancestor style that applies when any child is focusedA well-behaved input:
<input class="rounded-lg border border-gray-300 px-4 py-2 outline-none
focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
placeholder="Email address">The ring utilities draw an accessible halo around the element without shifting the layout the way borders can.
Style unavailable controls with disabled:, and validate visually with invalid:, required:, and checked::
<button disabled class="bg-blue-600 text-white px-5 py-2 rounded-lg
disabled:cursor-not-allowed disabled:opacity-50">
Submit
</button>
<input type="email" required class="border invalid:border-red-500 invalid:text-red-600">These map straight onto native CSS pseudo-classes, so the browser does the state tracking for you with zero JavaScript.
Often a child should react when its parent is hovered, like a card whose title changes color. Mark the parent with group, then use group-hover: on children:
<a href="#" class="group block rounded-xl border p-6 hover:shadow-lg">
<h3 class="font-semibold text-gray-900 group-hover:text-blue-600">Card title</h3>
<p class="text-gray-500">Hover anywhere on the card.</p>
<span class="text-blue-600 opacity-0 transition group-hover:opacity-100">Read more →</span>
</a>peer does the same trick for siblings, powering floating labels and toggle switches with pure CSS. Mark the input with peer, then style a following sibling with peer-focus: or peer-checked::
<input type="checkbox" class="peer sr-only" id="toggle">
<label for="toggle" class="block h-6 w-11 rounded-full bg-gray-300 peer-checked:bg-green-500"></label>Structural variants clean up lists and tables: first:, last:, odd:, and even:.
<ul>
<li class="border-b p-3 last:border-b-0 odd:bg-gray-50">Row</li>
</ul>Zebra striping and removing the final divider used to need extra CSS; now it is two utilities.
Variants compose with each other and with breakpoints. md:hover:scale-105 scales on hover only at tablet sizes and up; dark:hover:bg-gray-700 handles hover in dark mode. Order the prefixes from general to specific and Tailwind does the rest.
hover:bg-blue-700, focus:ring-2, disabled:opacity-50.focus-visible: with ring utilities.group-hover: styles children when the parent is hovered; peer-checked: styles siblings.first:, last:, odd:, even: handle structural styling like zebra stripes.Next up: Dark Mode, where a single dark: prefix gives your site a second theme.