Skip to content

Hover-Only Affordance: Why Buttons Disappear on Touch Screens

Adityo Guni Waluyo

An opacity-0 hover arrow hides the fact that a category row is clickable. Affordance lessons from replacing it with an always-visible View pill.

TL;DR

The portal's statistics category row was not interactive on desktop without a hover effect. A visible "View" pill was added to make the row's interactivity clear. This change improved usability across devices.

A few days ago I opened the portal I am building on my phone and tapped one of the statistics category rows. It worked. But when I looked at the same screen on desktop, the row sat there silently: no arrow, no "View" hint, nothing. The little emerald arrow only appeared when a cursor hovered over the row. On a phone, that cursor never exists.

Hover Is Not a Guaranteed Gesture

My first guess: it works on desktop, so it is fine. Wrong. CSS ships a hover media feature whose whole job is to test whether the primary input mechanism can hover at all; the none value spells out that many mobile devices can only emulate hover through an inconvenient long-press [1]. Hover is not something I can force to exist everywhere. NN/g puts it bluntly: hover is a gesture not available on touchscreens, and designers must address that asymmetry when a UI scales across devices [3].

The part that gets overlooked: this is not only a phone problem. On desktop too, a row that only "wakes up" under the cursor has the same issue at rest. NN/g's guidance is that icon labels should be visible at all times, without any interaction from the user, and that you should not rely on hover to reveal labels because it raises the interaction cost [4]. Translated to my case: if the only clickable hint appears after the pointer is already floating there, a user sitting still will never learn the row is tappable. NN/g also reminds us that part of your audience being unable to use a feature is not a reason to delete it for everyone; the requirement is graceful degradation, not removal [5].

The Fix: an Always-Visible "View" Pill

I replaced the opacity-0 group-hover:opacity-100 arrow with a small always-rendered emerald pill reading "View": rounded-full bg-emerald-600/10 px-2.5 py-1 text-[11px] font-bold ring-1 ring-emerald-600/15. The hover treatment stays, but only as a bonus: group-hover:bg-emerald-600 group-hover:text-white, plus a slight arrow nudge via group-hover:translate-x-0.5. The row link got aria-label="Lihat kategori {name}" (View category {name}) so screen readers announce a clear action name instead of a bare category string.

If you want to keep a purely hover-bound effect, the technical route is not deleting the effect but scoping it to devices that actually support it: @media (hover: hover) { ... }. MDN documents this pattern as the standard approach, and the feature has been Baseline Widely Available since December 2018 [1]. Tailwind exposes a hover: variant for every utility, stackable with breakpoints or dark mode when needed [2]. I chose the always-visible pill because it is simpler and closes the worst case outright: no action is left visible only after the user guesses.

What I Took Home

An affordance is a promise that must be visible at rest. Hover may enrich, say with a tooltip or a subtle shift, but it must never be the only route to an action. The cheap test: open the page on a phone and count how many actions vanish once the cursor is gone.

Sources

Related articles