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

PreferenceEffectStorage
High contrastStronger semantic shadcn tokens (--foreground, --muted-foreground, --border, sidebar, …)localStorage + cookie accessibility_high_contrast=1
Reduce motionForces reduced animations/transitions when the OS does not already asklocalStorage + 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)

QueryBehavior
prefers-reduced-motion: reduceMotion kill in CSS — no user toggle required
prefers-contrast: moreSame 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.

Authenticated layouts (app-sidebar-layout, app-header-layout) and the auth simple layout:

  1. SkipToContent — first focusable control, visible on :focus
  2. 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

ConcernLocation
Hookresources/js/hooks/use-accessibility-preferences.tsx
Menuresources/js/components/user-menu-content.tsx
Skip linkresources/js/components/skip-to-content.tsx
CSSresources/css/app.css (.high-contrast, @media)
Blade / cookiesresources/views/app.blade.php, HandleAppearance
Teststests/Feature/AccessibilityPreferencesTest.php, tests/Browser/AccessibilityPreferencesBrowserTest.php
Previous
Passkeys