Reach UI (@reach/dialog)
Accessible React modal dialog with built-in focus trapping, scroll locking, and WAI-ARIA compliant markup out of the box.
Repository Health
Technical Analysis
@reach/dialog is the modal/dialog primitive from the Reach UI component collection, a set of accessible, unstyled React components built directly against the WAI-ARIA Authoring Practices. It ships a high-level Dialog component alongside lower-level DialogOverlay/DialogContent/DialogInner primitives, so teams can either drop in a fully accessible modal in a few lines or compose their own styled dialog on top of the same accessible foundation.
Internally it delegates focus trapping to react-focus-lock and scroll locking to react-remove-scroll, and manages the accessibility details most hand-rolled modals get wrong: escape-to-dismiss, outside-click dismissal, returning focus to the triggering element on close, and marking background content aria-hidden while the dialog is open. Note that Reach UI’s maintainers have marked the project unmaintained (per the repo README), so it’s best suited for teams who want a stable, already-audited accessible dialog implementation rather than one receiving active feature development.
What You Get
- The high-level
Dialogcomponent plus lower-levelDialogOverlay/DialogContent/DialogInnerprimitives for custom composition - Built-in focus lock (via
react-focus-lock) and scroll lock (viareact-remove-scroll), each with an escape hatch to opt out if needed - Automatic
aria-hiddenmanagement for background content, plus Escape-key and outside-click dismissal wired to anonDismisscallback - A companion
styles.cssfile with baseline dialog styling that can be imported as-is or ignored in favor of fully custom CSS
Common Use Cases
- Accessible confirmation and alert modals
- Form overlays and multi-step dialogs
- Design-system foundations for a themed modal component
- Retrofitting WAI-ARIA compliance onto an existing custom modal
Under The Hood
Architecture
The package composes four layered components (DialogWrapper → DialogInner → DialogOverlay → DialogContent) around a single high-level Dialog export, all defined in packages/dialog/src/reach-dialog.tsx. DialogWrapper renders its children through a Portal (from the sibling @reach/portal package) and provides an isOpen flag via a lightweight context built with @reach/utils’ createContext helper. DialogInner wraps children in react-focus-lock’s FocusLock and react-remove-scroll’s RemoveScroll, composing dismissal handlers (Escape key, outside click) through a shared composeEventHandlers utility. DialogContent applies the actual ARIA attributes (role="dialog", aria-modal). A hand-rolled createAriaHider function walks the direct children of document.body at mount time to mark dialog siblings aria-hidden, restoring original values on cleanup, rather than relying on an external library for that behavior. The package depends on two sibling reach-ui packages (@reach/portal, @reach/polymorphic) and @reach/utils, reflecting the monorepo’s compositional design of small, independently publishable accessible primitives.
Tech Stack
Written in TypeScript against a React peer dependency of ^16.8.0 || 17.x (CI exercises React 16/17/18 via aliased package installs), bundled with tsup through a shared @reach-internal/dev config, and tested with Vitest plus vitest-axe for automated accessibility assertions and sinon for fake timers. The monorepo uses pnpm workspaces with Turborepo to orchestrate builds across roughly twenty sibling packages, and Changesets for versioning and publishing. Runtime dependencies are kept minimal and delegated to two focused libraries — react-focus-lock for focus trapping and react-remove-scroll for scroll locking — rather than reimplementing either.
Code Quality
The package ships two test files — one exercising interaction behavior (open/close state, focus handling, dismiss callbacks) and one running axe-core accessibility audits via vitest-axe — using @testing-library/react and @testing-library/user-event through a shared internal test-utils wrapper. Source is fully typed TypeScript with exported prop types and a shared Polymorphic.ForwardRefComponent typing pattern used across the monorepo for the as prop. ESLint and Prettier are configured at the monorepo root, and GitHub Actions workflows run tests and releases on push. That said, the project’s own README states Reach UI “is currently not maintained,” and its last release shipped as a prerelease in October 2022 — so while existing practices (types, tests, linting, CI) are solid, the package receives no active maintenance or bug fixes.
API Design
The public API is deliberately minimal: a single <Dialog isOpen onDismiss> component covers the large majority of use cases with just two required-in-practice props, while DialogOverlay/DialogContent are exposed separately only for consumers who need custom styling or the polymorphic as prop. This layered “simple thing simple, complex thing possible” API predates and closely parallels patterns later popularized by Radix UI and Headless UI. Nothing here is architecturally novel by current standards — headless, unstyled accessible-primitive libraries are now a well-established category — but @reach/dialog was an early, influential example of the pattern in the React ecosystem.
Used by 10 apps in this directory
CodeSandbox
Code Editors · Developer Tools
Instantly ready browser-based IDE that runs full npm dependency resolution and transpilation entirely client-side, with no server needed.
CodeSandbox
Code Editors · Developer Tools
Instantly ready browser-based IDE that runs full npm dependency resolution and transpilation entirely client-side, with no server needed.
CodeSandbox
Code Editors · Developer Tools
Instantly ready browser-based IDE that runs full npm dependency resolution and transpilation entirely client-side, with no server needed.
CodeSandbox
Code Editors · Developer Tools
Instantly ready browser-based IDE that runs full npm dependency resolution and transpilation entirely client-side, with no server needed.
CodeSandbox
Code Editors · Developer Tools
Instantly ready browser-based IDE that runs full npm dependency resolution and transpilation entirely client-side, with no server needed.
CodeSandbox
Code Editors · Developer Tools
Instantly ready browser-based IDE that runs full npm dependency resolution and transpilation entirely client-side, with no server needed.
Portainer
Devops
A lightweight, open-source web UI that puts Docker, Kubernetes, and Podman management within reach of any team—no CLI expertise required.
Portainer
Devops
A lightweight, open-source web UI that puts Docker, Kubernetes, and Podman management within reach of any team—no CLI expertise required.
Portainer
Devops
A lightweight, open-source web UI that puts Docker, Kubernetes, and Podman management within reach of any team—no CLI expertise required.