Search topics
Right-click, long-press and swipe actions with no button
A list's actions sit in a menu that only a right-click or a long press opens, or behind a swipe, and no row has a button that reaches them.
list.addEventListener('contextmenu', openMenu) The problem
People who use a keyboard, a switch, voice control or a screen reader, and anyone who doesn't guess the gesture, can't reach a row's actions.
A client portal lists shared documents. Right-click a file and the site's own menu opens at the pointer, in place of the browser's: Download, Make a copy, Delete. On a phone, a long press on the row opens the same menu. The narrow layout adds a second gesture: swipe a row to the left and a Delete button slides out from under it. Press Tab and focus goes past the list, straight to the help link under it. The rows can't take focus, and the menu's buttons exist only while the menu is open.
The actions have no control of their own. They run only from pointer events: a right-click, a press held for half a second, or a swipe. WCAG 2.1.1 asks for every function to work from a keyboard, and failure F54 names this pattern: pointer-specific event handlers as the only way to run a function. The swipe also fails 2.5.1. It is a path-based gesture, recognised only when the pointer moves left in a mostly straight line, and 2.5.1 asks for a way to do the same with a single pointer and no path, such as a tap on a button. A right-click and a long press are single-pointer actions, so they don't fail 2.5.1 themselves; what fails is that nothing else reaches the actions.
On many file lists, message threads and saved-item lists, the right-click menu is the only place to download, rename or delete. In a phone layout, where the row's buttons were dropped to save room, the swipe is often all that is left, so the same list can work on a desktop and lock people out on a phone.
4 of 10 groups affected
- No vision (affected)
- Low vision (not 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, switch and voice control users have no right button to press, no press to hold and no swipe to make, and the rows aren't in the Tab order, so no action can be reached at all.
- Without vision Screen reader users hear each row as plain text, with nothing to say it has actions, so they never learn that Download or Delete exists.
- Limited reach and strength People with tremors or little strength in their hands turn a long press into a scroll, or a swipe into a tap, and the actions never appear.
- Limited language, cognitive and learning abilities Nothing on a row says that a right-click, a long press or a swipe will show more, so people who don't already know the gesture never find the actions.
Scanners read the markup, and nothing in it is wrong: list items need no name, and the menu's buttons are real buttons. A contextmenu listener leaves no attribute, and a swipe is a pointer listener like any other. Whether the actions can be reached another way only shows when someone tries with the keyboard. Asked to fix it, AI tools often add tabindex="0" and role="button" to the rows so that Shift+F10 opens the menu. The rows get focus, but nothing shows that a menu is there, Enter does nothing on a "button", on a Mac no key opens it without a screen reader, and touch users still have to know the gesture.
Try it
Delete “Signed contract.pdf”: right-click it, press and hold it, or in Broken 2, swipe it to the left. Then put the mouse aside and try with Tab.
The panel shows what a screen reader announces. Without the gesture, can you reach Download, Make a copy and Delete, and do you hear which file they are for?
Broken Can you fix this?
Screen reader says:
(Tab to a file, or right-click one)
The fix
Keep the gesture as a shortcut, and give every row a button that reaches the same actions.
- 1Every action in the right-click, long-press or swipe menu is also on a button in the row, or in a menu that a button in the row opens.
- 2Those actions can be reached from the keyboard and say which row they act on.
- 3When an action removes a row, focus moves to the next row's controls, not the body.
Actions only in a right-click or long-press menu
<ul class="files"> <li data-file="invoice">Invoice 0412.pdf</li> <li data-file="contract">Signed contract.pdf</li> <li data-file="brand">Brand guide.pdf</li></ul><div class="file-menu" hidden> <button type="button" data-action="download">Download</button> <button type="button" data-action="copy">Make a copy</button> <button type="button" data-action="delete">Delete</button></div>An Actions menu button that opens the same actions
<li data-file="invoice"> <span id="file-invoice">Invoice 0412.pdf</span> <button type="button" popovertarget="menu-invoice" aria-haspopup="menu" aria-describedby="file-invoice"> Actions </button> <div id="menu-invoice" popover role="menu" aria-labelledby="file-invoice" class="file-menu"> <button type="button" role="menuitem" tabindex="-1" data-action="download">Download</button> <button type="button" role="menuitem" tabindex="-1" data-action="copy">Make a copy</button> <button type="button" role="menuitem" tabindex="-1" data-action="delete">Delete</button> </div></li>Why this fix: Make a gesture a shortcut, never the only way
The menu opens only from a right-click or a held touch. The rows can't take focus, so a keyboard user can't even reach the place where Shift+F10 would fire, and nothing on screen says a menu exists.
Every row now shows a button that opens the menu with a click, a tap, Enter or Space, and voice control users can say "Click Actions". The menu works like the browser's own context menu, which is what people reach for first: focus moves to the first action, Up and Down Arrow move through the actions, Home and End go to the first and last, Enter runs one, and Tab closes the menu and moves on. The popover attribute does the rest: the menu opens above the page, Escape or a click outside closes it, and focus returns to the Actions button. aria-haspopup="menu" tells a screen reader that the button opens a menu, and the menu takes its name from the file, so it is read as "Invoice 0412.pdf, menu" when focus enters it. The right-click and long-press menu can stay as it was, so nobody loses the shortcut. Position the menu under its button with CSS anchor positioning or a few lines of script; a popover is centred on the screen until you do.
Both pass on their own, for keyboard, pointer, touch and screen reader users. Buttons on the row take one press and show every action at a glance, but each row grows with every action and each button is a Tab stop, so a long list of rows becomes a long walk with Tab; choose them for two or three actions. The Actions button keeps one Tab stop per row and any number of actions in the same space, but takes two presses and hides the actions until it is opened; choose it for more actions or narrow layouts. Many lists mix the two: the action people use most as its own button, and the rest under Actions.
Keep the gesture. A right-click or a swipe is fast for the people who use it, and the criteria ask for another way, not a replacement. Cancel the browser's menu only on the rows, not on the whole page: the browser's menu holds Copy, Open in new tab, Translate and spelling suggestions, and people rely on them.
Making the rows focusable so that Shift+F10 or the Menu key opens the custom menu is a useful extra, not a fix. Those keys fire contextmenu on Windows and Linux, but a Mac has no such key, nothing on screen tells anyone to try, and touch users are left where they were.
Some details change with the kind of menu.
- Plain buttons in the popover: a popover of ordinary buttons with no menu roles, reached with Tab, also passes, and is less code. But people who open something that looks like a context menu try the arrow keys first, so if you keep plain buttons, don't announce a menu: leave out
aria-haspopup="menu"and the menu roles. - Component libraries: a menu component that sets
role="menu"must also move focus into the menu and between its items with the arrow keys. Check that it does before you rely on it. - Icon buttons: an Actions button drawn as "⋯" needs an accessible name, as icon-only buttons with no accessible name explains. Use words people would say, such as "More actions".
- Open state: browsers report a
popovertargetbutton as expanded or collapsed, so it needs noaria-expanded. Check it in the screen readers you support. - Menu styles: a
displayrule on the popover, such asdisplay: gridor aflexclass, beats the browser's rule that hides it, so the menu shows while closed. Set its display under:popover-open. - Swipes that follow the finger: a row that slides with the pointer while it is held is a drag rather than a swipe, and 2.5.7 applies as well. The same buttons fix it; see drag-and-drop boards that only a mouse can use.
- Selection: a checkbox on each row with a toolbar above the list that acts on the checked rows also passes, and suits lists where people act on several rows at once. It is a larger change, with a toolbar pattern of its own.
Focus needs care when an action removes a row. If focus was on that row's Delete or in its menu, move it to the same control on the next row, or the previous row when it was the last, or the list's heading when the list is empty. Otherwise it falls to the body, and a keyboard user starts again from the top of the page.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
5 checks.
Test with: Keyboard, Mouse, Touch, Screen reader, Speech input
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the list component or its menu library changes.
Limits
This is a starting pattern, not a guarantee for every application. Rows moved by dragging, long-press selection of several items, and gestures on maps and images such as pinch to zoom have their own needs, and are not covered here; for those, see pinch, drag and tilt gestures with no button. Menu libraries differ in how they handle focus, Escape and placement: test the complete journey with your target assistive technology.
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.