Hover, focus and other states

Tailwind CSS · Practical · 12 min

Variants are how utility classes handle interaction states that inline styles never could.

A variant is a prefix that applies a utility only under a condition: hover:bg-indigo-700 sets the background only on hover. Variants exist for nearly every pseudo-class:

  • interaction: hover:, focus:, focus-visible:, active:, focus-within:,
  • forms: disabled:, checked:, required:, invalid:, user-invalid:, placeholder: (the pseudo-element), file:,
  • structure: first:, last:, odd:, even:, nth-3:, not-first:, empty:,
  • open: for details and dialogs, before:/after: for pseudo-elements, aria-expanded: and data-[state=open]: for attributes.

In v4, hover: is wrapped in @media (hover: hover), so tapping on a touch screen doesn't leave sticky hover styles.

Styling based on other elements:

  • Mark a parent with group, then use group-hover: or group-focus: on any descendant.
  • Mark a sibling with peer and use peer-checked: or peer-invalid: on later siblings (it must come before them, as with the CSS ~ combinator).
  • has-checked: and has-[img]: use :has() to style an element by its contents.
  • Name groups to disambiguate nested ones: group/item with group-hover/item:.

Variants stack: dark:md:hover:bg-gray-700. In v4 they apply left to right, like the nested CSS they produce.

Example

<button class="rounded-md bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-700
               focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600
               active:scale-95 disabled:cursor-not-allowed disabled:opacity-50">
  Save
</button>

<!-- group: hover anywhere on the card changes the arrow -->
<a href="/docs" class="group block rounded-lg p-4 hover:bg-gray-50">
  Read the docs
  <span class="inline-block transition group-hover:translate-x-1">→</span>
</a>

<!-- peer: the checkbox must come before the elements it styles -->
<label>
  <input type="checkbox" class="peer sr-only">
  <span class="rounded-full bg-gray-200 px-3 py-1 peer-checked:bg-emerald-500 peer-checked:text-white">
    Notifications
  </span>
</label>

<ul><li class="py-2 not-last:border-b">One</li><li class="py-2 not-last:border-b">Two</li></ul>

Remember

  • Prefix any utility with a state: hover:, focus-visible:, disabled:
  • group/group-* styles children from a parent's state
  • peer/peer-* styles later siblings; the peer must come first
  • hover: only applies on devices that can hover

Watch out for

  • Putting the peer element after the element that uses peer-*; the sibling selector only looks backwards.

Check yourself

  1. A hidden checkbox should restyle the label text that follows it. Which classes?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: tailwindcss.com/docs/hover-focus-and-other-states