$npx -y skills add mgifford/accessibility-skills --skill keyboardLoad this skill for every project containing interactive UI elements — buttons, links, modals, dropdowns, sliders, tabs, carousels, or any custom widget. Under no circumstances create an interactive component that cannot be fully operated by keyboard alone. Absolutely always ensu
| 1 | # Keyboard Accessibility Skill |
| 2 | |
| 3 | > **Canonical source**: `examples/KEYBOARD_ACCESSIBILITY_BEST_PRACTICES.md` in `mgifford/ACCESSIBILITY.md` |
| 4 | > This skill is derived from that file. When in doubt, the example is authoritative. |
| 5 | |
| 6 | Apply these rules to every interactive UI element and feature. |
| 7 | |
| 8 | --- |
| 9 | |
| 10 | ## Core Mandate |
| 11 | |
| 12 | All interactive functionality must be fully usable with a keyboard alone — no |
| 13 | mouse or touch required, except where the underlying function genuinely |
| 14 | depends on path-based movement. Keyboard access is not equivalent to pressing |
| 15 | Tab through every control — native and composite widgets use different key |
| 16 | conventions, and users must understand how to operate custom widgets. |
| 17 | |
| 18 | --- |
| 19 | |
| 20 | ## Severity Scale (this skill) |
| 21 | |
| 22 | | Level | Meaning | |
| 23 | | --- | --- | |
| 24 | | **Critical** | Blocks task completion entirely for keyboard and AT users | |
| 25 | | **Serious** | Significantly impairs keyboard access; workaround unreasonable | |
| 26 | | **Moderate** | Creates friction for keyboard users; workaround exists | |
| 27 | | **Minor** | Best-practice gap; marginal keyboard impact | |
| 28 | |
| 29 | Do not assign severity from the rule alone — determine the actual effect on |
| 30 | the task. Consider whether the defect prevents reaching, operating, or leaving |
| 31 | a component; whether it blocks an essential task or only an optional feature; |
| 32 | whether focus is lost or placed misleadingly; and whether a reasonable |
| 33 | keyboard method remains available. |
| 34 | |
| 35 | --- |
| 36 | |
| 37 | ## Critical: No Keyboard Trap |
| 38 | |
| 39 | If focus can enter a component, users must be able to move focus away using |
| 40 | Tab, Shift+Tab, arrow keys, or Escape (depending on the component). If leaving |
| 41 | requires an unusual command, provide instructions before or within the component. |
| 42 | |
| 43 | **A keyboard trap with no exit is Critical.** Restricting focus temporarily is |
| 44 | NOT a trap when it is a modal dialog with a standard/documented exit: focus |
| 45 | moves in on open, Tab/Shift+Tab stay within it, a keyboard-operable control |
| 46 | closes/completes it, and closing returns focus to the invoking control or |
| 47 | another logical location. |
| 48 | |
| 49 | Test embedded editors, third-party widgets, iframes, media players, remote |
| 50 | desktops, and canvas applications carefully — they commonly consume Tab or |
| 51 | other keys and need an available, documented exit. |
| 52 | |
| 53 | --- |
| 54 | |
| 55 | ## Critical: All Interactive Elements Must Be Keyboard Reachable |
| 56 | |
| 57 | Every element that can be activated by mouse must be reachable and activatable |
| 58 | by keyboard. **Use native elements** — they provide keyboard behaviour, |
| 59 | focusability, semantics, states, and browser integration built in at zero |
| 60 | extra cost: |
| 61 | |
| 62 | ```html |
| 63 | <!-- Good: built-in keyboard support --> |
| 64 | <button type="button">Save</button> |
| 65 | <a href="/about">About</a> |
| 66 | |
| 67 | <!-- Avoid: requires full ARIA + JS to match native behaviour --> |
| 68 | <div role="button" tabindex="0">Save</div> |
| 69 | ``` |
| 70 | |
| 71 | Adding `role="button"` and `tabindex="0"` does not add button behaviour by |
| 72 | itself — a custom button must also implement Enter and Space activation, |
| 73 | disabled state, focus styling, and the expected accessible name/state. Test |
| 74 | the actual result, not merely whether a `keydown` handler exists. Mouse and |
| 75 | touch handlers must not be the only way to operate a control. |
| 76 | |
| 77 | --- |
| 78 | |
| 79 | ## Critical: Expected Key Behaviours |
| 80 | |
| 81 | Deviating from expected widget key behaviour is **Critical** — it breaks the |
| 82 | mental model AT users depend on. Do not override standard browser behaviour on |
| 83 | native controls without a documented need. Follow the current |
| 84 | [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/) for custom widgets (versioned |
| 85 | independently of WCAG — APG patterns are design guidance, not WCAG |
| 86 | requirements by themselves; always confirm the current version). |
| 87 | |
| 88 | | Control | Required keys | |
| 89 | | --- | --- | |
| 90 | | Button | `Enter`, `Space` | |
| 91 | | Link | `Enter` | |
| 92 | | Checkbox | `Space` to toggle | |
| 93 | | Radio group | Arrow keys to move between options; `Space` to select | |
| 94 | | `<select>` | Browser/platform conventions for navigation and selection | |
| 95 | | Menu / menubar | Arrow keys; `Enter` to activate item; `Escape` to close | |
| 96 | | Tab widget | Arrow keys between tabs; Tab moves into the active panel | |
| 97 | | Dialog | `Escape` to close; focus trapped inside while open | |
| 98 | | Combobox | Depends on editable/select-only behaviour; `Escape` commonly closes | |
| 99 | | Tree view | Up/Down navigate visible nodes; Right expands/enters; Left collapses/parent | |
| 100 | | Slider | Arrow keys to change value; `Home`/`End` for min/max; Page Up/Down for larger steps | |
| 101 | | Grid | Arrow keys move among cells; Tab usually enters/leaves the composite | |
| 102 | | `<details>` summary | `Enter`/`Space` toggles disclosure | |
| 103 | |
| 104 | Do not implement a widget from a summary table alone — use the |