From harness-claude
Guides implementation of keyboard-accessible interactive elements, focus management, and WAI-ARIA patterns for modals, menus, tabs, and custom widgets.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:a11y-keyboard-navigationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Ensure all interactive elements are reachable and operable via keyboard alone without requiring a mouse
Ensure all interactive elements are reachable and operable via keyboard alone without requiring a mouse
Use native interactive elements. <button>, <a>, <input>, <select>, and <textarea> are keyboard-accessible by default. They receive focus, respond to Enter/Space, and are announced by screen readers. Never recreate this behavior on <div> or <span>.
Provide a visible focus indicator. Users must see where focus is. Never remove the outline without providing an alternative.
/* Remove default only if providing a custom indicator */
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* Never do this without a replacement */
/* :focus { outline: none; } */
Use :focus-visible instead of :focus so the indicator appears only for keyboard users, not mouse clicks.
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav><!-- navigation --></nav>
<main id="main-content"><!-- content --></main>
</body>
.skip-link {
position: absolute;
left: -10000px;
}
.skip-link:focus {
position: static;
display: block;
}
tabindex values greater than 0 — they disrupt the natural flow. Use only tabindex="0" (add to tab order) and tabindex="-1" (programmatically focusable but not in tab order).// tabindex="0" — makes a non-interactive element focusable
<div role="listbox" tabIndex={0}>
// tabindex="-1" — focusable via JavaScript, not via Tab
<div id="error-message" tabIndex={-1} ref={errorRef}>
function handleKeyDown(e: React.KeyboardEvent) {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
focusNextItem();
break;
case 'ArrowUp':
e.preventDefault();
focusPreviousItem();
break;
case 'Home':
e.preventDefault();
focusFirstItem();
break;
case 'End':
e.preventDefault();
focusLastItem();
break;
case 'Escape':
closeMenu();
break;
}
}
function openModal() {
triggerRef.current = document.activeElement as HTMLElement;
setIsOpen(true);
// Focus the modal after render
requestAnimationFrame(() => modalRef.current?.focus());
}
function closeModal() {
setIsOpen(false);
triggerRef.current?.focus(); // return focus to trigger
}
function trapFocus(container: HTMLElement) {
const focusable = container.querySelectorAll<HTMLElement>(
'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
container.addEventListener('keydown', (e) => {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
}
WCAG requirements: SC 2.1.1 (Keyboard) requires all functionality to be operable via keyboard. SC 2.1.2 (No Keyboard Trap) requires users to be able to move focus away from any component. SC 2.4.7 (Focus Visible) requires a visible focus indicator.
tabindex values:
0: Element is added to the natural tab order based on DOM position-1: Element is focusable via element.focus() but not via Tab key1, 2, etc.): Avoid — they override natural tab order and create maintenance nightmaresRoving tabindex pattern: For composite widgets (tab lists, toolbars), only one item has tabindex="0" at a time. Arrow keys move tabindex="0" to the next item and tabindex="-1" to the previous. Tab moves focus out of the widget entirely.
Testing keyboard navigation: Unplug your mouse and use the application with keyboard only. Tab through every page, activate every button, fill every form, dismiss every dialog. If you get stuck or cannot see where focus is, there is a bug.
https://www.w3.org/WAI/WCAG21/Understanding/keyboard
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeDesigns keyboard navigation and focus management for interactive interfaces — forms, menus, modals, tabs, carousels, drag-and-drop, data tables, and custom components.
Ensures every interactive UI element is reachable and operable by keyboard — Tab, Enter/Space, Escape, arrow keys — per WCAG 2.1 Level A standards.
Verify full keyboard operability of interactive UI elements per WCAG 2.1 SC 2.1.1. Use when testing web or desktop applications for accessibility compliance.