# Shell: Keyboard and accessibility

Source: `apps/oshun/web/src/components/AccessibilityShell.tsx`,
`apps/oshun/web/src/components/KeyboardShortcutHelp.tsx`,
`apps/oshun/web/src/components/CommandPalette.tsx`,
`apps/oshun/web/src/components/FocusModeToggle.tsx`,
`apps/oshun/web/src/navigation/routes.ts` (`WEB_SHELL_ROUTE_PATHS`),
`apps/oshun/web/src/navigation/assistant-entry.ts`
(`dispatchOshunAssistantOpen`)

Global keyboard, focus, and screen-reader behavior. If something here is broken,
the whole product is broken for the people who depend on it.

## Skip links

`AccessibilityShell` renders skip links as the first focusable elements.

- [ ] **Tab from page load** — focus lands on first visible skip link
- [ ] **Skip targets** —
  - [ ] `#main-content` — "Skip to main content"
  - [ ] `#shell-nav` — "Skip to navigation"
  - [ ] `#domains` — "Skip to domains"
- [ ] **Activation** — Enter focuses target via `requestAnimationFrame`
- [ ] **Visible on focus** — high-contrast pill at top-left; never invisible
- [ ] **Fallback** — when targets absent, only `#main-content` is shown
- [ ] **Screen reader** — links announced as "Skip to <area>"

## Global keyboard shortcuts

Implemented in `AccessibilityShell` + `KeyboardShortcutHelp`. Press `Shift+?` to
open the help dialog (KeyboardShortcutHelp.tsx:74).

| Keys             | Action                                                  |
| ---------------- | ------------------------------------------------------- |
| `Alt+/`          | Focus shell search input (AccessibilityShell.tsx:203)   |
| `⌘K` / `Ctrl+K`  | Open command palette (command-palette/CommandPalette.tsx:794) |
| `Alt+1`–`Alt+5`  | Go to Home / Explore / Activity / Library / Profile by  |
|                  | index (AccessibilityShell.tsx:167-187)                  |
| `Alt+A`          | Open assistant overlay (`dispatchOshunAssistantOpen`,   |
|                  | AccessibilityShell.tsx:189-200)                         |
| `Esc`            | Close modals / overlays / menus                         |
| `Shift+?`        | Open keyboard shortcut help (KeyboardShortcutHelp.tsx:74)|
| `Alt+f`          | Toggle focus mode (`FocusModeToggle`, FocusModeToggle.tsx:185) |

There is no `g <letter>` chord handler; route navigation is `Alt+1..5`, not a
two-key `g`-prefixed chord.

For each binding, verify:

- [ ] **Fires** in the browser when not focused in a text input
- [ ] **Suppressed** when typing in `<input>`, `<textarea>`, `contenteditable`
- [ ] **No conflict** with browser/OS shortcuts on macOS, Windows, Linux
- [ ] **Help dialog** lists the binding with the correct keys
- [ ] **Discoverable** — `KeyboardShortcutHelp` reachable from menu or `?`

## Command palette

`CommandPalette.tsx` — opens via `⌘K` / `Ctrl+K`.

- [ ] **Open** — keystroke opens; focus traps inside; Esc closes
- [ ] **Search** — fuzzy match across commands; recents pinned at top
- [ ] **Categories** — navigate, create, search, assistant, settings
- [ ] **Keyboard nav** — Up/Down moves selection; Enter activates; Tab/Shift+Tab
      equivalent
- [ ] **Screen reader** — combobox role; selection announced
- [ ] **Mobile** — full-screen overlay; native keyboard pushes content up

## Focus management

- [ ] **Focus visible** — every interactive element has a visible focus ring (no
      `outline: none` without a replacement)
- [ ] **Focus trap in modals** — Tab cycles inside; Esc closes; focus returns to
      opener
- [ ] **Focus on route change** — moves to `#main-content` heading on navigation
- [ ] **No focus loss** — when an element is removed, focus moves to the next
      sensible target
- [ ] **`autofocus` discipline** — only on intentional entry points (sign-in
      email, search)

## ARIA & landmarks

- [ ] **`<main id="main-content">`** present on every routable page
- [ ] **`<nav id="shell-nav">`** for primary shell nav
- [ ] **`<nav>` for domain switcher** — labeled with `aria-label="Domains"`
- [ ] **Headings** — one `<h1>` per page; subsequent levels nested correctly
- [ ] **Buttons vs links** — button for actions, link for navigation; never
      `<div onClick>`
- [ ] **`aria-live`** — toasts (`polite`), errors (`assertive`), no other live
      regions
- [ ] **`aria-expanded`** — on toggles for menus, accordions
- [ ] **`aria-current="page"`** — on active shell-nav item
- [ ] **`aria-busy`** — on regions while loading

## Screen reader walk

Test on at least one of: VoiceOver (macOS/iOS), NVDA (Windows), TalkBack
(Android).

- [ ] **First nav** — page title, then main landmark, then content
- [ ] **Form fields** — label, input, error, hint all spoken
- [ ] **Live regions** — toast, offline banner, sync complete spoken at correct
      urgency
- [ ] **Dynamic content** — loading → loaded transitions announced
- [ ] **Tables/lists** — semantic roles, row/column count when applicable

## Color and contrast

- [ ] **WCAG 2.2 AA** — body text ≥ 4.5:1, large text ≥ 3:1, UI ≥ 3:1
- [ ] **Forced-colors / Windows high-contrast** — UI still usable; colors come
      from system
- [ ] **Color not the only signal** — error states use icon + text, not red
      alone
- [ ] **Dark mode** — same ratios met
- [ ] **Color-blind palettes** — green/red distinctions also use shape/text

## Motion and animation

- [ ] **`prefers-reduced-motion: reduce`** — page transitions, toast slides,
      carousel autoplay, parallax, decorative motion all disabled or reduced
- [ ] **No animation > 5 s** without an off control
- [ ] **No flashing > 3× / s** (seizure safety)

## Pointer / touch

- [ ] **Touch targets ≥ 44×44 px** on coarse pointers
- [ ] **Spacing** — adjacent targets separated by ≥ 8 px to prevent mis-taps
- [ ] **Hover-only** — no information conveyed by hover alone; keyboard
      equivalent exists
- [ ] **Long-press** — has a non-long-press alternative

## Focus mode

`FocusModeToggle.tsx` — minimize chrome for reading.

- [ ] **Toggle** — `Alt+f` or button in header
- [ ] **Active** — chrome reduced; nav hidden; theme adjusts
- [ ] **Exit** — same toggle, Esc, or navigation
- [ ] **Persists per route** — not globally sticky
- [ ] **Screen reader** — announces "Focus mode on/off"

## Language

- [ ] **`<html lang>`** matches active locale
- [ ] **Language switcher** (`LanguageSwitcher`) updates `lang` and reloads
      strings
- [ ] **RTL support** — `dir` reverses; layout mirrors where designed
- [ ] **Hreflang alternates** — set per route for SEO

## Cross-references

- [01-app-shell.md](./01-app-shell.md)
- [05-notifications.md](./05-notifications.md) — toast live-region policies
- Feature spec: [`V1/features.md`](../../V1/features.md)

## Open questions / known gaps

- [ ] Confirm `aria-keyshortcuts` annotations present on shortcut-bound buttons
- [ ] Stand up a regular axe-core / pa11y CI sweep so per-view files can
      reference a baseline rather than re-running the same checks
