Hover, focus and other states
A variant is a prefix with a colon that applies a utility only in a certain state. hover:bg-indigo-500 changes the background while the cursor is over the element. In plain CSS you would write a separate rule with the :hover pseudo-class.
Hover
The "Add to cart" button gets lighter on hover:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<button class="rounded-lg bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-500">
Add to cart
</button>Hover over the button — the background turns lighter. In Tailwind 4 hover: only applies on devices with a mouse: on a phone the button does not "stick" in the hover state after a tap.
Focus
When a user moves through the page with the Tab key, the focused element must stand out. focus: applies on any focus, focus-visible: only on keyboard focus:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<input class="rounded-lg border border-gray-300 px-3 py-2 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 focus:outline-none" placeholder="Email">
<button class="ml-2 rounded-lg bg-indigo-600 px-4 py-2 text-white focus-visible:ring-4 focus-visible:ring-indigo-300 focus-visible:outline-none">
Subscribe
</button>Click into the field — the border turns blue and a soft ring appears. Press Tab to move to the button: a ring appears around it, but not when you click it with the mouse.
The main variants
| Variant | When it applies |
|---|---|
hover: | The cursor is over the element |
focus: | The element has focus |
focus-visible: | Keyboard focus |
active: | The element is being pressed |
disabled: | A field or button is disabled |
first:, last: | First or last among its siblings |
odd:, even: | Odd or even |
placeholder: | The hint text in a field |
List rows
odd: and last: save you separate classes for striping and for the last row:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<ul class="w-64">
<li class="border-b border-gray-200 p-2 odd:bg-gray-50 last:border-0">Headphones</li>
<li class="border-b border-gray-200 p-2 odd:bg-gray-50 last:border-0">Speaker</li>
<li class="border-b border-gray-200 p-2 odd:bg-gray-50 last:border-0">Charger</li>
</ul>Odd rows are gray, and the last one has no bottom line.
Combining variants
Variants stack from left to right: md:hover:bg-indigo-500 works only on a wide screen on hover, and disabled:opacity-50 dims a disabled button:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<button disabled class="rounded-lg bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-500 disabled:cursor-not-allowed disabled:opacity-50">
Out of stock
</button>Common mistake
Removing the focus outline with outline-none and giving nothing in return:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<button class="rounded-lg bg-indigo-600 px-4 py-2 text-white focus:outline-none">Buy</button>Now nobody can see where the focus is when using Tab, and keyboard users struggle with the site. If you remove the default outline, always add your own: focus-visible:ring-2.