Skip to content

Hover and Focus States

Tailwind applies interaction states like hover, focus, and active using simple class prefixes, no custom CSS or JavaScript required. This lesson covers the most useful state variants and how to keep them accessible.

What Are State Variants in Tailwind?

A state variant is a prefix that applies a utility only when an element is in a particular state, such as being hovered, focused, or active. Tailwind generates these variants automatically for nearly every utility class.

State variants compile to real CSS pseudo-classes like :hover and :focus, so they work exactly like hand-written CSS, just without writing any yourself.

<button class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400">
  Hover or Focus Me
</button>

hover:bg-blue-700 darkens the background on mouse hover; focus:ring-2 adds a visible focus ring for keyboard users.

State Variant Syntax

class="utility hover:utility focus:utility active:utility"
  • hover: applies styles while the pointer is over the element.
  • focus: applies styles while the element has keyboard or programmatic focus.
  • active: applies styles while the element is being pressed/clicked.
  • focus-visible: applies only when focus should be visibly indicated (usually keyboard navigation).
  • State variants can be combined with responsive prefixes, like md:hover:bg-blue-700.

State Variants Cheatsheet

The most common interaction-state prefixes you'll use on buttons, links, and form fields.

Variant Example Triggers When
hover: hover:bg-slate-100 Pointer hovers over the element
focus: focus:ring-2 Element receives focus
focus-visible: focus-visible:outline Focus should be visibly shown (usually keyboard)
active: active:scale-95 Element is being clicked/pressed
disabled: disabled:opacity-50 Element has the disabled attribute
visited: visited:text-purple-600 A link has been visited
group-hover: group-hover:text-blue-600 A parent with class group is hovered
peer-focus: peer-focus:block A sibling with class peer is focused
first: / last: first:mt-0 Element is the first/last child
checked: checked:bg-blue-600 Checkbox/radio input is checked

Group and Peer States

group and peer let a child element react to the state of a parent (group) or sibling (peer). Add group to the parent, then use group-hover: on any descendant to react to hovering the parent.

<a href="/article" class="group flex items-center gap-2">
  <span class="group-hover:underline">Read the article</span>
  <span class="transition-transform group-hover:translate-x-1">→</span>
</a>

Hovering anywhere on the link underlines the text and slides the arrow, even though the arrow itself isn't hovered.

focus vs focus-visible

focus: applies any time an element has focus, including mouse clicks in some browsers. focus-visible: applies only when the browser determines focus should be visibly shown, typically keyboard navigation. Using focus-visible: avoids showing a focus ring after a mouse click while still supporting keyboard users.

<button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500">
  Accessible Button
</button>

This removes the default outline for mouse clicks but keeps a clear ring for keyboard focus.

Combining States With Breakpoints

State and responsive prefixes stack together in any order Tailwind supports, letting you scope an interaction to a specific screen size.

<a class="text-slate-700 md:hover:text-blue-600" href="/pricing">
  Pricing
</a>

Hover color changes only apply at the md breakpoint and up, since hover isn't meaningful on touch devices.

Keeping Interactive States Accessible

Never remove focus indicators without replacing them with an equally visible alternative. Keyboard and switch-device users rely on focus rings to know where they are on the page.

<button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500">
  Save Changes
</button>
  • Never use focus:outline-none without an alternative focus style like focus-visible:ring-2.
  • Ensure hover-only interactions have a keyboard-accessible equivalent (focus, click, or tap).
  • Test your interface using only the Tab key to confirm every interactive element is reachable and visible.

Common Mistakes

  • Removing the default focus outline with focus:outline-none and never adding a replacement focus style.
  • Relying only on hover: for critical information that touchscreen users would never be able to trigger.
  • Using focus: when focus-visible: would give a cleaner experience for mouse users.
  • Forgetting that group and peer require the exact group/peer class name on the related element to work.
  • Not testing interactive states with keyboard-only navigation before shipping.

Key Takeaways

  • State variants like hover:, focus:, and active: apply utilities conditionally based on interaction.
  • group-hover: and peer-focus: let elements react to a parent's or sibling's state.
  • focus-visible: shows focus styles primarily for keyboard users, avoiding rings after mouse clicks.
  • State and responsive prefixes can be combined, such as md:hover:underline.
  • Never remove focus indicators without providing an accessible replacement.

Pro Tip

Default to focus-visible:ring-2 instead of focus:ring-2 on custom buttons and links; it gives keyboard users a clear indicator without adding a visible ring on every mouse click.