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