Two buttons in a confirmation dialog look simple: Cancel and Confirm. Keyboard behavior makes the component a small state machine.
A recent MonkeyCode change gives us a concrete example. Issue #862 and PR #863 add these shortcuts to the slash-command confirmation:
ArrowLeft -> focus Cancel
ArrowRight -> focus Confirm
The reviewed implementation at commit c58bcd4 moves focus through button refs. That is a useful extra interaction. It is not a replacement for the dialog's accessibility foundation.
Keep the baseline first
For an alert-style confirmation, users still need:
- an accessible name and description;
- focus moved inside when the dialog opens;
-
TabandShift+Tabconstrained to dialog controls; -
Escapeto dismiss when cancellation is allowed; - visible focus;
- focus returned to the trigger after close;
- actual buttons whose labels explain the actions.
The WAI-ARIA Authoring Practices Alert Dialog Pattern describes the modal semantics and keyboard foundation. Left/right mapping is a product shortcut, not a required AlertDialog convention. That means we must not steal keys from the established behavior around it.
Isolate the extra mapping
The companion keyboard.mjs starts with a pure function:
export function arrowAction(key) {
if (key === "ArrowLeft") return "cancel";
if (key === "ArrowRight") return "confirm";
return null;
}
The event handler ignores unrelated and modified keys:
export function handleDialogArrow(event, controls) {
const action = arrowAction(event.key);
if (!action || event.altKey || event.ctrlKey || event.metaKey) return false;
event.preventDefault();
controls[action].focus();
return true;
}
Notice what is absent: no handler for Tab, Shift+Tab, Escape, or Enter. The native <dialog> and buttons in the minimal demo retain their normal jobs. In a React component, use a well-tested modal/dialog primitive for focus containment and dismissal, then add this narrow handler to its content.
A complete minimal dialog
The included dialog.html uses native semantics:
<button id="open">Run slash command</button>
<dialog id="confirm" aria-labelledby="title" aria-describedby="description">
<h2 id="title">Run this slash command?</h2>
<p id="description">The task may change files in the workspace.</p>
<form method="dialog">
<button id="cancel" value="cancel">Cancel</button>
<button id="accept" value="confirm">Confirm</button>
</form>
</dialog>
On open, the demo focuses Cancel. That conservative default avoids placing initial focus on a potentially destructive action. On close, it restores focus to the trigger and announces the result in an aria-live status.
Test the mapping and the interaction
Run the pure mapping test:
node test-keyboard.mjs
Expected output:
PASS arrows move focus; Tab, Escape, and modified arrows remain untouched
Then serve the directory over HTTP and test dialog.html with keyboard only. The manual checklist is:
- Open from the trigger; focus is visible on Cancel.
- Press Right; focus moves to Confirm without executing it.
- Press Left; focus returns to Cancel.
- Use Tab and Shift+Tab; focus stays within the modal.
- Press Escape; the dialog closes and focus returns to the trigger.
- Reopen, focus Confirm, press Enter; the button activates once.
- Zoom to 200% and repeat without losing labels or focus indication.
Also test with a screen reader. Arrow keys can participate in screen-reader navigation modes, so the control must remain understandable without discovering the shortcut. Never make the arrows the only way to choose an action.
The design rule is pleasantly small: add shortcuts as an enhancement around semantic controls, and prove that the keys you did not handle still work.
Disclosure: I'm a MonkeyCode user, not affiliated with the project. The product behavior is based on the linked public issue, PR, and pinned source; the standalone mapping tests were run locally, not as a full MonkeyCode accessibility audit.

Top comments (3)
"prove that the keys you did not handle still work", this should be printed on a poster somewhere. It matters for AI agents too: they navigate dialogs through the same semantics. There's a CHI paper this year (A11y-CUA) where agent task success dropped from 78% to 42% keyboard-only. Same baseline, one more reason to get it right.
How do you handle cases where the dialog has multiple focusable elements and the user navigates with arrow keys? I'd love to swap ideas on this.
The keyboard-only result sounds interesting. Do you have a link to the A11y-CUA paper or the exact study setup? Iād like to understand which dialog semantics caused the largest drop before drawing conclusions from the percentage.