# Keyboard Accessibility

All functionality operable via keyboard, no keyboard traps, character key shortcuts.

## Criteria

| Standard | Criteria                                                                          |
| -------- | --------------------------------------------------------------------------------- |
| WCAG 2.2 | 2.1.1 Keyboard (A), 2.1.2 No Keyboard Trap (A), 2.1.4 Character Key Shortcuts (A) |
| RGAA 4.1 | 7.1–7.5 Scripts                                                                   |

### Keyboard — WCAG 2.1.1 (A) / RGAA 7.1, 7.3

All functionality must be operable via keyboard. Use semantic elements (`<button>`, `<a>`, `<input>`) which get keyboard support for free. Custom widgets need keyboard handlers for Enter, Space, Arrow keys as appropriate. All script-driven features must work with both keyboard and pointer.

### No Keyboard Trap — WCAG 2.1.2 (A) / RGAA 7.3

Keyboard focus must always be movable away from any component. Modals: close on Escape, focus returns to trigger. Never create tab loops without an exit.

### Character Key Shortcuts — WCAG 2.1.4 (A) / RGAA 12.10

Single-character keyboard shortcuts must be: turnable off, OR remappable to include a modifier key (Ctrl, Alt), OR active only when the component has focus.

---

## RGAA 4.1 Theme 7 — Scripts (Detailed Criteria and Tests)

### Criterion 7.1 — Assistive technology compatibility (Level A)

Each script must be, if necessary, compatible with assistive technologies.

**WCAG mapping:** 2.5.3 Label in Name (A), 4.1.2 Name, Role, Value (A)

#### Test 7.1.1

Each script that generates or controls an interface component must satisfy one of these conditions:

- The name, role, value, settings, and state changes are accessible to assistive technologies via an accessibility API
- An accessible interface component providing access to the same functionality is present in the page
- An accessible alternative providing access to the same functionality exists

**Test methodology:**

1. Find all interface components generated or controlled via JavaScript in the document
2. Verify that:
   - The component has a role consistent with its usage (typically a button or link)
   - The component has an explicit name
   - The component name is consistent with the state of the controlled feature or content (e.g. for a show/hide toggle)
3. Otherwise, verify the presence of an accessible interface component providing the same functionality
4. Otherwise, verify the presence of an accessible alternative providing the same functionality
5. If so, the test is validated

#### Test 7.1.2

Each script that generates or controls an interface component must satisfy one of these conditions:

- The interface component is correctly rendered by assistive technologies
- An accessible alternative providing access to the same functionality exists

**Test methodology:**

1. For each component that passed test 7.1.1, verify the component is correctly rendered by assistive technologies
2. Otherwise, verify that an accessible alternative provides access to the same functionality
3. If so, the test is validated

#### Test 7.1.3

Each script that generates or controls an interface component must satisfy these conditions (except special cases):

- The component has a relevant name
- The accessible name of the component contains at least the visible label
- The component has a relevant role

**Test methodology:**

1. For each component that passed test 7.1.1, verify the component has:
   - A relevant name (visible label)
   - A relevant role
2. If the component has an accessible name, verify it is relevant and contains at least the visible label
3. If so, the test is validated

**Cas particuliers (7.1.3):**

- Punctuation and capital letters in the visible label text may be ignored in the accessible name without consequence
- When visible label text serves as a symbol: the accessible name must express the function conveyed by the symbol (e.g. "B" in a text editor should have accessible name "Bold", ">" could mean "Next" or "Play video" depending on context). Exception: mathematical symbols may be used literally (e.g. "A>B")
- If the visible label represents a mathematical expression, math symbols may be reproduced literally in the accessible name

**Notes techniques (7.1):**

Criterion 7.1 implements the concept of "compatible with assistive technologies" as defined by WCAG, as well as the use of WAI-ARIA to make a component or feature accessible. Correct WAI-ARIA usage is verified through tests 7.1.1, 7.1.2, 7.1.3.

Important: in an HTML5 environment, many components may require JavaScript to function. Consequently, providing an alternative to a JavaScript component that cannot be made accessible must use a method specific to the component in question, allowing it to be replaced by an accessible alternative (and reactivated). Disabling JavaScript for the entire page is NOT accepted as a valid method, unless it does not compromise the use of other components.

---

### Criterion 7.2 — Script alternatives (Level A)

For each script with an alternative, the alternative must be relevant.

**WCAG mapping:** 1.1.1 Non-text Content (A), 4.1.2 Name, Role, Value (A)

#### Test 7.2.1

Each script starting with a `<script>` tag and having an alternative must satisfy one of these conditions:

- The alternative between `<noscript>` and `</noscript>` provides access to similar content and functionality
- The page displayed when JavaScript is disabled provides access to similar content and functionality
- An alternative page provides access to similar content and functionality
- Server-side scripting provides access to similar content and functionality
- An alternative present in the page provides access to similar content and functionality

**Test methodology:**

1. Find alternatives to JavaScript functionality in the page
2. Search the page for alternatives to a JavaScript component or functionality made available
3. Disable JavaScript in the document and find the proposed alternatives
4. For each proposed alternative, verify it provides access to the same content and similar functionality
5. If so, the test is validated

#### Test 7.2.2

Each non-textual element updated by a script (in the page or in a frame) and having an alternative must satisfy these conditions:

- The alternative of the non-textual element is updated
- The updated alternative is relevant

**Test methodology:**

1. Find all non-textual elements updated by a JavaScript function in the document
2. If the non-textual element has an alternative, verify that:
   - The alternative is updated when the non-textual content is updated
   - The updated alternative is relevant
3. If so, the test is validated

---

### Criterion 7.3 — Keyboard and pointer device control (Level A)

Each script must be controllable by keyboard and by any pointing device (except special cases).

**WCAG mapping:** 1.3.1 Info and Relationships (A), 2.1.1 Keyboard (A), 2.4.7 Focus Visible (AA)

#### Test 7.3.1

Each element with a script-controlled event handler must satisfy one of these conditions (except special cases):

- The element is accessible by keyboard and any pointing device
- An element accessible by keyboard and any pointing device that performs the same action is present in the page

**Test methodology:**

1. Find all elements in the document with JavaScript event handlers (e.g. click, focus, mouseover, blur, keydown, touch...)
2. Verify the element is accessible via keyboard:
   - It is reachable with the Tab key
   - If the element manages a simple action, it is activatable with Enter
   - If the element manages a complex action, it is usable with the keyboard (typically with arrow keys)
3. Otherwise, verify that a keyboard-accessible element performing the same action is present in the page
4. Verify the element is accessible by any pointing device (mouse, touch, stylus...)
5. Otherwise, verify that an element accessible via pointing device performing the same action is present in the page
6. If so, the test is validated

#### Test 7.3.2

A script must not suppress focus from an element that receives it. This rule must be respected (except special cases).

**Test methodology:**

1. Activate, one after another, all elements capable of receiving focus
2. Verify that focus is not suppressed via a JavaScript function
3. If so, the test is validated

**Cas particuliers (7.3):**

When the functionality depends on the use of an event handler with no universal equivalent (e.g. a freehand drawing application cannot be made keyboard-controllable), the criterion is not applicable.

---

### Criterion 7.4 — Context changes (Level A)

For each script that initiates a context change, the user must be warned or have control.

**WCAG mapping:** 3.2.1 On Focus (A), 3.2.2 On Input (A)

#### Test 7.4.1

Each script that initiates a context change must satisfy one of these conditions:

- The user is warned by text about the script action and the type of change before it is triggered
- The context change is initiated by an explicit button (`input` of type `submit`, `button`, or `image`, or a `<button>` element)
- The context change is initiated by an explicit link

**Test methodology:**

1. Find all JavaScript events in the document that initiate a context change, for example:
   - Dynamic update of form fields
   - Opening a new page upon activation of a `<select>` option
   - AJAX update of an essential part of the page
   - Automatic launch of a video player after playlist selection
   - Focus manipulation that changes the user's current position in the page
2. Verify that:
   - The user is warned by a message about the script action and the type of change before triggering; OR
   - The context change is initiated by an explicit button (input type submit, button, image, or `<button>` element); OR
   - The context change is initiated by an explicit link
3. If so, the test is validated

---

### Criterion 7.5 — Status messages (Level AA)

Status messages must be correctly rendered by assistive technologies.

**WCAG mapping:** 4.1.3 Status Messages (AA)

#### Test 7.5.1

Each status message that informs of success, the result of an action, or the state of an application must use `role="status"`.

**Test methodology:**

1. Find all messages in the document that qualify as status messages
2. For each message, determine the nature of the information it carries:
3. If the message informs of success, the result of an action, or the state of an application, verify the containing element:
   - Uses `role="status"`; OR
   - Uses `aria-live="polite"` and `aria-atomic="true"`
4. If the message presents a suggestion or warns of an error, verify the containing element:
   - Uses `role="alert"`; OR
   - Uses `aria-live="assertive"` and `aria-atomic="true"`
5. If the message indicates process progression, verify the containing element:
   - Uses one of `role="log"`, `role="progressbar"`, or `role="status"`; OR
   - Uses `aria-live="polite"` if the intent is to signal the equivalent of a `log` role; OR
   - Uses `aria-live="polite"` and `aria-atomic="true"` if the intent is to signal the equivalent of a `status` role
6. If so, the test is validated

#### Test 7.5.2

Each status message that presents a suggestion or warns of an error must use `role="alert"`.

**Test methodology:** Same as test 7.5.1.

#### Test 7.5.3

Each status message that indicates process progression must use one of `role="log"`, `role="progressbar"`, or `role="status"`.

**Test methodology:** Same as test 7.5.1.

**Notes techniques (7.5):**

WAI-ARIA roles `log`, `status`, and `alert` implicitly have `aria-live` and `aria-atomic` values. Per the WAI-ARIA 1.1 specification:

- `aria-live="polite"` on a status message can serve as a `log` role
- `aria-live="polite"` + `aria-atomic="true"` on a status message can serve as a `status` role
- `aria-live="assertive"` + `aria-atomic="true"` on a status message can serve as an `alert` role

This holds provided the nature of the status message matches the implicit correspondence. For a status message indicating process progression with a graphical progress bar, an explicit `role="progressbar"` is required.

---

## Patterns

**Alpine.js** — dropdown with escape, enter, space:

```html
<div x-data="{ open: false }" @keydown.escape="open = false">
  <button @click="open = !open" :aria-expanded="open" aria-controls="dropdown">Toggle</button>
  <div x-show="open" x-trap.noscroll="open" id="dropdown" role="menu">
    <!-- Arrow key navigation inside -->
  </div>
</div>
```

**React** — modal with focus trap and escape:

```tsx
function Modal({ isOpen, onClose, children }: ModalProps) {
  const ref = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (isOpen) ref.current?.focus()
  }, [isOpen])

  if (!isOpen) return null

  return (
    <div ref={ref} role="dialog" aria-modal="true" tabIndex={-1} onKeyDown={(e) => e.key === 'Escape' && onClose()}>
      {children}
    </div>
  )
}
```

Essential keys: Tab (forward), Shift+Tab (backward), Enter/Space (activate), Escape (dismiss), Arrow keys (within widgets).
