# Accessibility Focus Management Mechanica now includes a reusable focus-management layer for modal UI, command surfaces, and future inspector drawers. The goal is to keep keyboard and assistive-technology behaviour predictable as the shell grows more complex. ## Added primitives - `src/utils/focusManagement.ts` - Finds focusable descendants in browser tab order. - Skips hidden, `inert`, `aria-hidden`, disabled, `aria-disabled`, and negative-tabindex elements by default. - Provides helpers for moving focus, restoring focus, and wrapping `Tab` / `Shift+Tab` inside a contained region. - `src/hooks/useFocusTrap.ts` - React hook for modal surfaces. - Supports initial focus targets, Escape handling, focus restoration, and `preventScroll`. - `src/components/ui/Dialog.tsx` - Accessible dialog primitive with `role="dialog"`, `aria-modal`, labelled title, optional description, focus trap, overlay dismissal, Escape dismissal, and body scroll locking. ## Dialog usage ```tsx import { useRef, useState } from "react"; import { Button } from "../components/ui/Button"; import { Dialog } from "../components/ui/Dialog"; export function ExampleDialog() { const [open, setOpen] = useState(false); const primaryActionRef = useRef(null); return ( <> Continue} >

Use dialogs for focused decisions or compact reference panels where the user should not continue interacting with the catalogue behind the overlay.

); } ``` ## Command palette integration pattern The command palette should keep focus inside the palette while open and return focus to the invoking button or shortcut origin when closed. ```tsx const panelRef = useRef(null); useFocusTrap(panelRef, { enabled: open, initialFocus: "[data-command-input]", onEscapeKey: () => setOpen(false), }); ``` Prefer `aria-activedescendant` for the command results list when focus remains on the input, or a roving `tabIndex` pattern when each result is directly focusable. Avoid placing positive tabindex values in application code; the utility supports them because third-party and legacy markup may contain them, but normal DOM order is easier to reason about. ## Manual QA checklist For every modal, palette, and future drawer: 1. Open the surface with keyboard only. 2. Confirm focus moves to the intended first control or to the panel fallback. 3. Press `Tab` through every control and verify focus wraps at the end. 4. Press `Shift+Tab` from the first control and verify focus wraps to the last control. 5. Press `Escape` and verify the surface closes only when Escape dismissal is enabled. 6. Confirm focus returns to the opener after close. 7. With a screen reader, confirm the announced name matches the visible title and the description is read when present. 8. Confirm background page scrolling is locked while a dialog is open and restored after close. ## Automated coverage `tests/focusManagement.test.ts` covers the most failure-prone focus behaviours: - hidden and disabled filtering; - positive, zero, implicit, and negative tabindex ordering; - programmatic container fallback focus; - first/last focus movement; and - forward/backward focus wrapping for trapped regions.