$48
Search topics
Card actions that only appear on hover
A product grid keeps its Save and Add to bag buttons hidden until the pointer rests on a card, so nothing else ever shows them.
.card:hover .actions { visibility: visible; } The problem
People who use a keyboard, a switch, a screen reader, a touch screen or a magnifier can't find or use a card's actions, because only a resting pointer shows them.
A shop's product grid is drawn clean: a photo, a name and a price on each card, and nothing else. Rest the pointer on a card and a row of actions fades in under the price: Save, and Add to bag. Move away and it is gone. The design keeps the grid quiet, and the actions are there for anyone with a mouse.
Put the mouse aside and press Tab. Focus moves from one card's name to the next card's name. Save and Add to bag are never reached, because the row is hidden with visibility: hidden until the card is hovered, and a hidden button takes no focus. On a phone, there is no pointer to rest: a tap on the card's photo or name opens the product page, and the actions never show on the grid at all.
A second version of the same design hides the row with opacity: 0 instead, and slides it in over the bottom of the card, on top of the price. Now Tab does reach Save, but the button is fully transparent while focus is on it, so a keyboard user sees nothing change and doesn't know what Enter would do. Under a magnifier, the row covers the price as soon as the pointer arrives, and Escape does nothing.
The actions exist only for a resting pointer. WCAG 2.1.1 Keyboard asks for every function to be operable from the keyboard, and failure F54 describes the script version of the same thing: functionality that only a pointing-device event reaches. With visibility: hidden or display: none, the buttons are out of the Tab order. With opacity: 0, they are in it, but invisible: a keyboard user can press a button they can't see, which is no better. And a touch screen can't hover at all, so phone users get neither version.
When the revealed row covers other content, 1.4.13 Content on Hover or Focus applies too. Content that appears on hover and obscures what was there must be dismissible without moving the pointer or focus, usually with Escape. Actions that slide in over the price fail that, because nothing closes them.
The pattern is common on product grids, file lists, photo galleries and dashboards, wherever a designer wants the resting state quiet. The same card often carries its only Save or Delete there.
5 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (affected)
- Cognitive (affected)
- Seizures (not affected)
- Limited manipulation Keyboard and switch users have no pointer to rest on the card, so the actions never appear, or focus lands on a button they can't see.
- Without vision Screen reader users move with the keyboard too, and visibility: hidden also removes the buttons from the accessibility tree, so they never hear that a card has a Save at all.
- Limited reach and strength On a phone there is no hover. A tap on the card follows its link before any action has shown, and someone whose hand can't hold still over a card never sees them on a laptop either.
- Limited vision Magnifier users see one corner of a card at a time. The actions slide in over the name and price as the pointer arrives, and there is no way to put them away to read what is under them.
- Limited language, cognitive and learning abilities Nothing on a card says that resting on it shows more, so people who don't already know the pattern never find the actions.
The markup is fine: real buttons with names, inside the card. A static scan sees them and passes. The failure is in a CSS rule and in what never happens: nobody hovers, and nothing else shows the row. A tool has to hover a card, see the buttons appear, then focus them and see whether they are still there. When asked to fix it, AI coding tools can add tabindex="0" to the card, which puts the card in the Tab order but still shows nothing, or swap visibility for opacity, which moves the failure from one form to the other.
Try it
Rest the pointer on a card, then put the mouse aside and press Tab to reach Save.
The panel under the grid says what has focus and whether it can be seen. Can you reach Save and Add to bag without a mouse, and see them when you do?
Broken Can you fix this?
$18
The fix
Show the actions on focus and on touch screens too, in their own space, or show them on every card all the time.
- 1Every action a hover reveals is also shown when the card or the action has focus.
- 2On a screen with no hover, the actions are shown without any pointer state.
- 3Revealed actions sit in their own space, or Escape hides them without moving focus.
Actions hidden until the card is hovered
<article class="card"> <a href="/tote"><img src="tote.jpg" alt="Linen tote, natural">Linen tote</a> <p class="price">$48</p> <div class="actions"> <button type="button">Save</button> <button type="button">Add to bag</button> </div></article>Shown on focus and on touch screens too, in their own row
This fix adds no HTML.
Why this fix: Make a gesture a shortcut, never the only way
Only :hover reveals the row. Hidden with visibility: hidden, the buttons are out of the Tab order, so the keyboard never reaches them, and a touch screen never hovers.
:focus-within matches the card while any element inside it has focus, so the row appears as soon as Tab lands on the card's link, and the buttons after it are visible when focus reaches them. visibility must flip in the same rule, or the buttons stay out of the Tab order. The hover: none media query is true on phones, and on tablets used by touch, where the row would otherwise never show. The row keeps its own place under the price, so it covers nothing when it appears and 1.4.13 doesn't ask for a way to dismiss it. The pointer reveal stays as it was, for everyone who has a pointer.
Both pass. The reveal on focus and touch keeps the design as drawn for pointer users and costs three CSS rules, but each new place the card appears needs the same rules, and the hover: none fallback means the phone layout looks different from the desktop one, which the design file should show. Always-shown actions change the resting look, and ask the designer to draw the buttons so that they belong there. For a card whose actions matter, such as Add to bag on a shop grid or Delete on a file list, draw them in: an action a shop depends on shouldn't rely on anyone finding a hover. Keep the reveal for secondary actions on a grid that would otherwise be crowded, with the focus and touch rules in place.
A third shape is an always-shown menu button on each card, with the actions in a menu it opens. It takes the least space of all, and the right-click, long-press and swipe actions with no button guide shows the menu in full.
The reveal is a state, and a state needs drawing. Add a focus variant of the card beside the hover one, with the actions shown and the focus ring on the first button, and a touch variant with the actions always present, so that the developer has something to build the :focus-within and hover: none rules from. For the always-shown version, draw the buttons at their real size: 24 by 24 pixels is the minimum target under 2.5.8, and the outlined pill needs 3:1 against the card for its border and 4.5:1 for its text. Write the contrast ratios beside the swatches, so they survive a palette change.
Give each button a name that says which product it acts on when the grid has many: a visually hidden span, "Save Linen tote", or aria-describedby pointing at the card's name, so a screen reader user moving through buttons alone isn't told "Save, Save, Save". The focus ring on a revealed button needs 3:1 against the card like any other; see missing focus indicators on custom buttons. If the whole card is a link and the buttons sit inside it, nested interactive content is invalid HTML and reads badly: keep the link on the name and photo, and the buttons as siblings of it.
Under forced colors, opacity and visibility still apply, so a hidden row stays hidden there too; the fixes above work unchanged. A row shown with display: none and display: flex works the same way as visibility in the fixes, but can't be faded in; pick one and apply it to every card component.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Design ideas
Actions can stay quiet without hiding.
Five ways to draw the same card’s actions. Point at each card, then put the mouse aside and Tab through them: three show their buttons to every way of using the page, and two only to a mouse.
Passes
- Outlined pills, always shown The guide’s second fix: small pills in the card’s own ink, 24 pixels tall, that fill on hover and focus. Nothing is hidden, so the keyboard and a phone get the same card.
- Quiet text actions, underlined on hover The calmest resting look: plain words in #555555, 7.45:1 on white, with no box until hover or focus underlines them. Keep at least 24 pixels of height and space between them so they stay easy to hit.
- Revealed on hover and on focus The guide’s first fix: the row still waits for the pointer, but :focus-within shows it as soon as Tab reaches the card, a screen with no hover shows it all the time, and it has its own row, so it covers nothing.
Rule of thumb: Make a gesture a shortcut, never the only way
Fails
- Hidden until the card is hoveredWCAG 2.1.1 visibility: hidden takes the buttons out of the Tab order, so the keyboard skips them, and a touch screen never hovers to show them.
- Transparent row over the cardWCAG 2.4.7 Kept at opacity: 0, the buttons are still in the Tab order, so focus lands on Save while nothing on screen shows it. On hover the row slides over the price, and no key puts it away.
Verify the fix
5 checks.
Test with: Keyboard, Mouse, Touch, Zoom, Screen reader
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the card component or the grid's theme changes.
Limits
This is a starting pattern, not a guarantee for every application. The reveal rules assume the actions are plain buttons inside the card; a library's card component may render them elsewhere in the DOM, where :focus-within won't see them. Test on your real grid, with a keyboard, a phone and a magnifier.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.