Reach UI FocusScope vs Custom Focus Traps for Non-Linear Navigation
0 reputation · 23 Feb 2022, 14:42 UTC
0 reputation · 23 Feb 2022, 14:42 UTC
When building complex modal overlays in React, managing focus is critical for WAI-ARIA compliance. Reach UI provides the FocusScope component to automatically handle tab-indexing and prevent focus from leaking into the background page.
While the declarative API of FocusScope simplifies standard dialog implementations, certain complex UI patterns require non-linear navigation or imperative focus shifts that may conflict with the library's built-in automation. There is a design trade-off between utilizing the standardized behavior of Reach UI and implementing a custom focus trap to gain granular control over the navigation sequence.
For a project targeting React 17+ and requiring strict adherence to WAI-ARIA 1.2 specifications:
FocusScope support custom navigation orders that deviate from the DOM sequence?28775 reputation · 23 Feb 2022, 16:02 UTC
1. FocusScope does not provide a declarative way to alter the tab order. It traps focus inside its subtree and preserves the natural DOM sequence. 2. Use a custom focus trap only when the design requires a navigation order that cannot be expressed by the DOM. For most modal dialogs the convenience of FocusScope outweighs the overhead of a custom implementation. 3. When dynamic focusable elements or conditional skipping are involved, a small imperative wrapper around FocusScope is usually sufficient; full custom traps are reserved for very complex patterns.
FocusScope automatically adds tabindex=-1 to elements outside the subtree and restores focus to the first focusable child when the component mounts. The navigation sequence it enforces follows the DOM order; it does not expose props or callbacks to reorder or skip elements. Therefore, if a modal requires that pressing Tab jump from a button to a distant panel, the only way to achieve that with FocusScope is to add imperative logic (e.g., keydown listeners that call focus() on a custom element).
Building a custom trap means you must manage a list of focusable elements, handle Tab and Shift+Tab, and ensure focus never leaks to the background. This adds code complexity and maintenance risk, especially when elements are added or removed dynamically.
In practice, the trade‑off is simple: if the DOM order already matches the desired navigation flow, stick with FocusScope. If you need to skip, reorder, or conditionally focus elements, wrap FocusScope in a lightweight manager that listens for key events and calls focus() on the next element in a custom list. Only when the pattern is very complex (e.g., multiple nested portals, conditional focus based on user state) should you consider a full custom trap implementation.
<FocusScope> as-is.<FocusScope> in a component that:
keydown listener for Tab and Shift+Tab.focus().If the modal contains:
In such cases, a dedicated custom trap library (e.g., focus-trap-react) or a hand‑rolled implementation is preferable.
Do any of your modal overlays add or remove focusable elements at runtime (e.g., via user input, API responses, or conditional rendering)? If so, that may influence whether a custom trap is necessary.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.