Skip to content

@holmdigital/components — Dialogs & Modals

Open source React component. Accessible React Dialog component with focus trapping, ESC close, and WCAG 2.1.2 compliance. No keyboard traps.

The Dialog and Modal components provide accessible, focus-trapping overlays using the native HTML <dialog> element. They handle managing aria-modal, focus restoration, and backdrop interaction automatically.

Regulatory Context

No Keyboard Trap (2.1.2) is a critical "Blocker" issue. Users must always be able to escape a modal (usually ESC key). Ideally, focus should loop within the modal (Focus Trap). Failure to implement this correctly is one of the most severe accessibility violations possible.

Standard Dialog

A non-modal dialog (conceptually) or a standard modal. Our Dialog component uses the native <dialog> element.

Code Example

<Dialog isOpen={isOpen} onClose={() => setIsOpen(false)} title="Edit Profile" description="Make changes to your profile here." > <YourContent /> </Dialog>

Alert Modal

Destructive actions should use the variant="alert" prop to warn users.

Code Example

<Dialog variant="alert" title="Are you sure?" isOpen={isOpen} onClose={close} > <p>This action cannot be undone.</p> <Button variant="danger">Delete</Button> </Dialog>

Form in Modal

Modals trap focus correctly, making them safe for complex forms.

Edit Profile

Make changes to your profile here. Click save when you're done.

This is a standard dialog content area. You can put anything here.

Delete Account

Are you sure you want to delete your account? This action cannot be undone.

All your data will be permanently removed from our servers forever. This is a very long warning text to test wrapping.

Subscription Details

We will send the receipt here

Dialog Props

Props Reference

PropTypeDefaultDescription
isOpen *boolean-Controls visibility. Handles body scroll locking when true.
onClose *() => void-Called when the dialog closes (ESC key, backdrop click, or native close event). Must update the isOpen state.
title *string-Linked to the dialog container via aria-labelledby. Essential for context.
description string-Linked via aria-describedby. Use for additional instructions.
variant 'default' | 'alert''default''alert' produces role="alertdialog" (instead of role="dialog") and a red title. Use for destructive confirmations.
initialFocusRef RefObject<HTMLElement>-Element to focus on open. Defaults to the first focusable child. Recommended for destructive confirms (point at Cancel, not Confirm).
closeOnBackdropClick booleantrueClose when the user clicks the backdrop (the area outside the dialog content).
closeOnEscape booleantrueClose when the user presses Escape. Set to false for forms with unsaved changes.

Accessibility Features

  • Focus Trap & RestoreTab and Shift+Tab cycle within the dialog. On open, focus moves to initialFocusRef or the first focusable element. On close, focus is restored to the element that opened the dialog.
  • Escape to CloseNative dialog Escape handling, with an opt-out via closeOnEscape={false} for forms with unsaved changes.
  • Ref-counted Scroll LockBody scroll is locked while open, and stacked dialogs/modals/toasts compose safely — the lock is only released when every requester has released it.
  • role="dialog" / role="alertdialog"variant="alert" upgrades the role to alertdialog for destructive confirmations. aria-modal="true" is always set.
  • Unique ARIA Ids per Instancearia-labelledby and aria-describedby use useId() so multiple dialogs on the same page never collide.
  • Backdrop Click via Target IdentityBackdrop clicks are detected via e.target === dialog (not geometry), so transformed/scaled dialogs and overflowing children behave correctly.