295 words, 2 min read

When you add a disabled attribute to a <button> element, you probably expect it to just... look disabled. But there's a subtle trap that catches a lot of developers: your hover: styles still apply visually, even when the button is disabled.

The problem

Consider a typical Tailwind button:

<button class="bg-gray-200 hover:bg-gray-300 hover:cursor-pointer" disabled>
Click me
</button>

Even though the button is disabled and won't fire any events, the hover:bg-gray-300 and hover:cursor-pointer classes still apply on hover. The cursor becomes a pointer and the background changes — giving the user a false signal that the button is interactive.

The fix: enabled:

Tailwind ships with an enabled: variant that maps directly to the CSS :enabled pseudo-class. Swap your hover: styles for enabled:hover: and the problem disappears:

<button class="bg-gray-200 enabled:hover:bg-gray-300 enabled:hover:cursor-pointer" disabled>
Click me
</button>

Now those styles only apply when the button is not disabled. No JavaScript, no conditional class logic, no extra wrapper — just a single variant prefix.

Why this matters

The :enabled pseudo-class is the semantic opposite of :disabled. It's supported in all modern browsers and has been in CSS for years, but it's easy to overlook because disabled states are often handled with opacity or a wrapper div instead.

Using enabled:hover: keeps your intent explicit in the markup and makes disabled state handling a one-liner in your component library.

In practice (Phoenix / LiveView)

If you have a reusable button component, this is the ideal place to apply it. Instead of:

"hover:bg-gray-300 hover:cursor-pointer"

Write:

"enabled:hover:bg-gray-300 enabled:hover:cursor-pointer"

Now any caller that passes disabled as an attribute gets correct visual behavior automatically — no special-case classes needed at the call site.

Small trick, but it saves a prop, a conditional, and a subtle UX bug all at once.