Features
Accessibility
Externa ships user-first accessibility in the authenticated shell: high contrast and reduce motion in the user menu, OS media queries honored without a toggle, and a skip link into main#main-content. Target is WCAG 2.2 AA on P0 flows (login, shell, collections/items/files, drawers, settings, AI FAB) — not a full axe CI suite.
Related
Personal theme (light/dark): Account settings. Shell layout: Architecture.
User preferences
| Preference | Effect | Storage |
|---|---|---|
| High contrast | Stronger semantic shadcn tokens (--foreground, --muted-foreground, --border, sidebar, …) | localStorage + cookie accessibility_high_contrast=1 |
| Reduce motion | Forces reduced animations/transitions when the OS does not already ask | localStorage + cookie accessibility_reduce_motion=1 |
UI: user menu → Accessibility submenu (summary Off / N on), same checkmark pattern as Appearance/Language.
Hook: resources/js/hooks/use-accessibility-preferences.tsx (sibling of useAppearance). Blade boot via HandleAppearance + app.blade.php stamps high-contrast / reduce-motion on <html> before paint (cookies are not encrypted — see bootstrap/app.php).
OS media queries (always on)
| Query | Behavior |
|---|---|
prefers-reduced-motion: reduce | Motion kill in CSS — no user toggle required |
prefers-contrast: more | Same token bumps as .high-contrast without forcing the menu toggle |
The user Reduce motion class only adds force-on when the OS preference is off. High contrast from the OS does not require the menu class.
Skip link & landmark
Authenticated layouts (app-sidebar-layout, app-header-layout) and the auth simple layout:
SkipToContent— first focusable control, visible on:focus- Target
main#main-content(tabIndex={-1}for programmatic focus)
Sidebar navigation uses role="navigation" with an accessible name.
P0 scope vs follow-ups
In scope (shipped): preferences, CSS, skip/landmark, high-impact aria/labels on P0 surfaces, Pest smoke (cookie classes + browser menu toggles), this doc.
Out of scope (follow-up): font size / dyslexia fonts, jest-axe / eslint-jsx-a11y zero-warnings, automated contrast CI.
Source map
| Concern | Location |
|---|---|
| Hook | resources/js/hooks/use-accessibility-preferences.tsx |
| Menu | resources/js/components/user-menu-content.tsx |
| Skip link | resources/js/components/skip-to-content.tsx |
| CSS | resources/css/app.css (.high-contrast, @media) |
| Blade / cookies | resources/views/app.blade.php, HandleAppearance |
| Tests | tests/Feature/AccessibilityPreferencesTest.php, tests/Browser/AccessibilityPreferencesBrowserTest.php |