SIGN IN SIGN UP

a11y: Accessibility support for all components

## Summary

- Adds `Role`, `aria_*` attributes, and `on_a11y_action` handlers to all
major UI components using the AccessKit integration in the latest GPUI
- Covers interactive controls, navigation, overlays, data display,
containers, and form components (~25 component files)
- Adds `Root` component `Role::Application` landmark so the
accessibility tree has a proper application root below Window
- Adds `script/run-story-macos` helper to build and launch Story gallery
as a signed macOS `.app` bundle for VoiceOver testing

## Component role mapping

| Component | Role | Extra attributes |
|-----------|------|-----------------|
| `Button` | `Button` / `Link` | `aria_label`, `aria_selected` |
| `Toggle` / `ToggleGroup` | `Button` + `aria_toggled`, `Toolbar` | — |
| `Checkbox` | `CheckBox` | `aria_toggled`, `aria_label` |
| `Radio` / `RadioGroup` | `RadioButton`, `RadioGroup` |
`aria_selected`, `position_in_set`, `size_of_set` |
| `Input` | `TextInput` / `MultilineTextInput` | — |
| `NumberInput` | `SpinButton` | `aria_numeric_value` |
| `Slider` | `Slider` | `aria_numeric_value`, min/max,
`aria_orientation`, Increment/Decrement a11y actions |
| `Tab` / `TabBar` | `Tab` + `aria_selected`, `TabList` | — |
| `PopupMenu` / `AppMenuBar` | `Menu`, `MenuBar` | — |
| `MenuItem` | `MenuItem` | `aria_selected` |
| `Dialog` / `AlertDialog` | `Dialog` (configurable), `AlertDialog` | —
|
| `Alert` | `Alert` | — |
| `Progress` | `ProgressIndicator` | `aria_numeric_value`, min/max |
| `List` / `ListItem` | `List`, `ListItem` | `aria_position_in_set`,
`aria_size_of_set`, `aria_selected` |
| `Table` / Header / Body / Row / Cell | `Table`, `RowGroup`, `Row`,
`ColumnHeader`, `Cell` | `aria_row_index`, `aria_column_index` |
| `Accordion` / item header | `Button` + `aria_expanded` | — |
| `Combobox` | `ComboBox` | `aria_expanded` |
| `Stepper` / `StepperItem` | `ListItem` | `aria_position_in_set` |
| `BreadcrumbItem` | `Link` / `ListItem` | — |
| `Root` | `Application` | — |

## Verification guide

### Prerequisites

AccessKit only builds the full accessibility tree when an assistive
technology is **active**. Accessibility Inspector alone is not
sufficient — you must enable VoiceOver first.

### macOS — VoiceOver + Accessibility Inspector

```bash
# 1. Build and launch Story gallery as a signed .app bundle
./script/run-story-macos
```

```
# 2. Enable VoiceOver  (this activates AccessKit)
Cmd + F5

# 3. Open Accessibility Inspector
#    Xcode → Xcode menu → Open Developer Tool → Accessibility Inspector
#    (or: open -a "Accessibility Inspector")

# 4. In Accessibility Inspector, click the target crosshair icon,
#    then hover over or click elements in the Story window.
#    You should see the full hierarchy:
#    Application > Window > Application > Button / CheckBox / Tab / ...

# 5. Tab through interactive elements — VoiceOver announces each one.

# 6. Test Slider keyboard control:
#    Focus the Slider, then press Up/Down arrows (Increment/Decrement a11y actions).
```

<img width="1407" height="828" alt="SCR-20260706-pzbf"
src="https://github.com/user-attachments/assets/32e7b567-c075-4c9f-a672-0daf3964c9a3"
/>

https://github.com/user-attachments/assets/d5793e31-e902-42cc-be2b-dc6781ec62dc

### Key things to verify

- **Button**: role `AXButton`, label matches button text
- **Checkbox**: role `AXCheckBox`, value changes on click
- **Toggle**: role `AXButton`, `AXValue` reflects checked state
- **Radio group**: each item reports `AXPositionInSet` / `AXSizeOfSet`
- **Tab bar**: `AXTabGroup` containing `AXTab` items, selected tab has
`AXValue = 1`
- **Dialog**: role `AXDialog` when open, focus trapped inside
- **Slider**: `AXSlider` with `AXMinValue` / `AXMaxValue` / `AXValue`,
responds to VoiceOver Increment/Decrement

### Windows — NVDA

```powershell
cargo run
# Enable NVDA, Tab through the window.
# NVDA should announce component type and label on focus.
```

### Linux — AT-SPI (Orca)

```bash
cargo run
# Enable Orca, Tab through the window.
```

---

> **Note**: `active_flag` starts `false` and becomes `true` only on
first AT query.
> If Inspector shows only `Window` with no children, ensure VoiceOver is
on,
> then wait ~1 second and click **Refresh** in Accessibility Inspector.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Codex <codex@openai.com>
J
Jason Lee committed
f0abdd9f8535c3c7639cc53f666e02318adbf64e
Parent: dbc038c