Skip to content

About

Headless React modal/dialog component with ARIA attributes with focus trapping, keyboard navigation, and scroll locking — no styling opinions included

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

accessible-modal-react

Headless React modal dialog primitives with focus trapping, Escape handling, backdrop dismissal, scroll locking, ARIA wiring, and focus restoration.

The package ships no CSS. Consumers own layout, colors, animation, and spacing.

Install

Clone and build the source release (Node.js 20 or later):

git clone https://github.com/jare20895/accessible-modal-react
cd accessible-modal-react
npm ci
npm run typecheck
npm test
npm run build
npm pack

To use the built package, run npm install /absolute/path/to/accessible-modal-react/accessible-modal-react-0.1.0.tgz in your React app. These instructions use the GitHub source release; npm registry publication is not part of this release.

React and React DOM are peer dependencies. The release tests use React 18.3.1.

Basic Usage

import { useRef } from "react";
import { Modal, useModal } from "accessible-modal-react";

export function PublishDialog() {
  const triggerRef = useRef<HTMLButtonElement>(null);
  const modal = useModal();

  return (
    <>
      <button ref={triggerRef} onClick={modal.open}>
        Publish
      </button>

      <Modal
        isOpen={modal.isOpen}
        onClose={modal.close}
        triggerRef={triggerRef}
        aria-labelledby="publish-title"
        aria-describedby="publish-description"
      >
        {({ close }) => (
          <div className="dialog-panel">
            <h2 id="publish-title">Publish whitepaper</h2>
            <p id="publish-description">This will make the current draft public.</p>
            <button onClick={close}>Cancel</button>
            <button onClick={close}>Publish</button>
          </div>
        )}
      </Modal>
    </>
  );
}

Props

Prop Type Default Notes
isOpen boolean required Controls whether the modal renders.
onClose () => void required Called by Escape key, backdrop click, or render-prop close helpers.
children ReactNode or render prop required Render prop receives { close }.
triggerRef RefObject<HTMLElement> current active element Focus target after close.
initialFocusRef RefObject<HTMLElement> first focusable element Initial focus target after open.
closeOnEscape boolean true Enables Escape key dismissal.
closeOnBackdropClick boolean true Enables backdrop dismissal.
onOpen () => void none Called when the modal opens.
aria-label string none Use when no visible heading labels the dialog.
aria-labelledby string none ID of the visible dialog title.
aria-describedby string none ID of the dialog description.

Accessibility Behavior

  • Renders with role="dialog" and aria-modal="true".
  • Supports aria-label, aria-labelledby, and aria-describedby.
  • Moves focus into the dialog on open.
  • Traps Tab and Shift+Tab within the dialog.
  • Restores focus to triggerRef or the previously active element after close.
  • Locks body scroll while open.
  • Applies aria-hidden="true" to body siblings outside the portal while open.
  • Closes on Escape and backdrop click unless disabled.

Styling

No styles are included. The portal renders a backdrop wrapper with data-modal-backdrop=""; the dialog element is an unstyled child. Style your own content normally:

[data-modal-backdrop] {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgb(0 0 0 / 0.5);
}

.dialog-panel {
  max-width: 32rem;
  background: white;
  padding: 1.5rem;
}

Development

npm install
npm run typecheck
npm test
npm run build

Non-goals

  • No bundled CSS, animation, or visual variants.
  • No nested modal stack manager.
  • No drawer, sheet, popover, or alert-dialog specialization in v1.

About

Headless React modal/dialog component with ARIA attributes with focus trapping, keyboard navigation, and scroll locking — no styling opinions included

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages