tailthemes
all-access
notes
6 min

Clickable should look clickable

A person reaching toward a wide plain card holding three bars, with one small highlighted chevron at the card's right edge
A person reaching toward a wide plain card holding three bars, with one small highlighted chevron at the card's right edge

A card with a click handler on a div works in code and goes unfound on screen. Give it a signifier: a hover state, an arrow, or a visible label that says what opens. Build it as a real link stretched over the card, so a keyboard reaches it too. Then ask a new teammate to point at what they think is clickable.

Why does nobody find the clickable card?

Picture a project card on a dashboard. Your team knows the whole card opens the project, because the onClick is right there in the component. The customer sees a grey rectangle with text in it and goes looking for an Open button.

The interaction works. Nobody finds it. That gap is the thesis of this series: code is cheap now, and understanding is the bottleneck.

It is also a common shape in generated components. Ask an agent to make a card clickable and the shortest diff is a handler on the outer div, with nothing on screen changed. The code review passes, because the code is correct. What the person sees was never part of the review.

What is the difference between an affordance and a signifier?

An affordance is what a thing lets you do. A signifier is the clue that tells you it can. The card above has the affordance (it can be clicked) and is missing the signifier.

Don Norman drew that line in a 2008 column for ACM Interactions, "Signifiers, not affordances", and made it central to the 2013 revised edition of The Design of Everyday Things. His definition: "A 'signifier' is some sort of indicator, some signal in the physical or social world that can be interpreted meaningfully." The affordance can exist with no signal at all, and then for the person in front of it, it does not exist.

The cost of a weak signal has been measured. In a 2017 Nielsen Norman Group eyetracking study, Kate Moran tested 71 people on nine sites, each in a strong-signifier and a weak-signifier version. On the weak versions people spent 22% more time on the page and made 25% more fixations.

Which signal should a clickable card carry?

Usually one of three, and each tells the reader something different:

  • A hover state says the card reacts, but only once a mouse arrives, and a phone has no hover.
  • An arrow or chevron says the card goes somewhere, and it is visible at rest on every device.
  • A visible label such as "Open project" or "Read the case" says exactly what happens next.

We read the whole-card links in our own code to see which ones we use. Five components, read from source on 17 October 2026:

ComponentWhereHoverArrowVisible labelRing on the whole card
Selected work rowFoundry (free)background tint, label underlinesup-rightRead the caseyes
Article cardGroveborder step, press shaderightnone, reading time sits by the arrowyes
Product cardKilnphoto scales upnonenone, the product name is the linkyes
Programme cardCircuitphoto brightens, border stepnonenoneyes
Theme card on the /templates shelvestailthemes.comborder step, name turns greennonenoneyes
Five whole-card links in our own code. Every one has a hover state and a focus ring on the whole card; only one carries all three signals.

Three of the five signal with hover alone, our own marketplace shelf among them. On a phone those three cards carry no signal beyond being cards. Foundry's arrow points up and to the right on purpose: that row leaves the page for the case entry, and the theme keeps a plain right arrow for links that stay.

Point at everything

Open one screen and ask a teammate who has never seen it to point at everything they think is clickable. Then list what is: run grep -rn "<div[^>]*onClick" src and add every link and button on that screen. Anything on your list and missing from theirs is your backlog.

How do you build a whole-card link a keyboard can reach?

A div with a click handler is not in the tab order, and it fails in four ways at once:

  • Tab skips it.
  • Enter does nothing, because nothing has focus.
  • A screen reader does not announce it as a link.
  • Middle-click and "copy link" have no address to work with.

WCAG 2.1.1 (Keyboard, level A) asks for every function to be operable from a keyboard, so this is a failure, not a style choice.

Four small rounded tiles in a row, with a curved arrow hopping from the first to the second, then arcing over the highlighted third tile to land on the fourth
Tab moves from one focusable thing to the next. A card built as a div with a click handler is not one of them, so the keyboard jumps straight over it.

Wrapping the whole card in one <a> fixes the keyboard and creates two new problems. Heydon Pickering's Inclusive Components shows the first: every word in the card becomes the link's name, so a screen reader reads the card out in one breath. The second is HTML's own rule: a link cannot contain another link or button, so the card can never hold a second action.

The pattern our themes use puts a real link on the title and stretches it over the card with a pseudo-element. The card draws the ring when the link inside it has keyboard focus:

<article class="relative rounded-xl border border-border bg-card p-6 hover:bg-muted
  has-[a:focus-visible]:outline-2 has-[a:focus-visible]:outline-offset-2 has-[a:focus-visible]:outline-ring">
  <h3 class="font-semibold">
    <a href="/projects/atlas" class="focus-visible:outline-none after:absolute after:inset-0">
      Atlas redesign
    </a>
  </h3>
  <p class="text-muted-foreground">12 open tasks, due Friday</p>
  <p class="mt-4 text-sm font-medium text-primary">Open project <span aria-hidden="true">→</span></p>
</article>

The link's name stays "Atlas redesign", the whole card takes the click, and the label and arrow carry the signal. Any other button inside the card needs relative z-10 to sit above the stretched layer.

One Tailwind v4 trap sits here. If you put the ring on the link itself, outline-none on the same element sets the outline style to none, and a focus-visible:outline-2 beside it computes invisible. Add focus-visible:outline-solid. An August audit of our own catalog found four rings broken exactly this way.

When should a card stay quiet?

When it is not the main thing on the screen. However, don't decorate everything: if every card carries an arrow, a label and a lift, the signals cancel out and nothing stands out. Signal the primary action and keep the rest quiet.

A grid of six rounded cards in two rows, holding plain bars and panels, with only the top-left card carrying a small highlighted chevron
Six cards, one signal. The arrow marks the card that matters on this screen; the rest stay quiet, so the one cue still reads.

Convention works as a signifier too, the kind Norman's column calls social. A grid of product photos with prices under them reads as clickable in any shop, which is why Kiln can lean on hover alone and stay legible. A project card on an internal dashboard has no such convention to borrow, so it needs the arrow or the words.

If you want to see the full pattern in a theme, Grove's journal cards carry the arrow, the press state and the ring on the whole card, and Foundry, which is free, has the selected-work row with all three signals.

The table reads our code, not our customers. We have not measured whether the hover-only cards on our own shelves lose clicks, and the study above tested websites in general, not cards. The stretched link has a limit of its own: text inside the card can no longer be selected with a mouse drag, because the link's layer sits on top of it.

share this

Get the next one

Posts go up at most weekly, and only with data behind them. New themes ship in the same mail.

  • we confirm first
  • one-click out
  • no tracking