Morphos
Overlays

Dialog

An accessible modal dialog with focus trapping and scroll locking.

Interactive example

Open in Storybook

Installation

npm install @morphos/overlays
pnpm add @morphos/overlays
yarn add @morphos/overlays
bun add @morphos/overlays

Import

import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogTitle,
  DialogDescription,
  DialogClose,
} from '@morphos/overlays'

Usage

@Component()
class MyComponent extends StatefulComponent {
  @State() dialog = new Dialog()

  render() {
    return (
      <>
        <DialogTrigger dialog={this.dialog}>Open</DialogTrigger>
        <DialogContent dialog={this.dialog} aria-labelledby="my-dialog-title">
          <DialogTitle id="my-dialog-title">Confirm</DialogTitle>
          <DialogDescription>Are you sure you want to proceed?</DialogDescription>
          <DialogClose dialog={this.dialog}>Cancel</DialogClose>
          <button onClick={() => this.dialog.closeDialog()}>Confirm</button>
        </DialogContent>
      </>
    )
  }
}

Compound components

ComponentDescription
DialogRoot state manager. Owns open state and exposes openDialog() / closeDialog() / toggle().
DialogTrigger<button> that opens the dialog. Sets aria-haspopup="dialog", aria-expanded, and data-open.
DialogContentRenders into a Portal with a sibling backdrop <div data-morphos-backdrop>. Sets role="dialog" and aria-modal="true". Applies focus trap and scroll lock while open.
DialogTitleRenders as <h2> by default (configurable via as). Pair its id with aria-labelledby on DialogContent.
DialogDescriptionRenders as <p>. Pair its id with aria-describedby on DialogContent.
DialogClose<button> that calls closeDialog().

Props — Dialog

PropTypeDefaultDescription
openboolean—Controlled open state. When set, the consumer owns the state.
defaultOpenbooleanfalseInitial open state in uncontrolled mode.
onOpenChange(open: boolean) => void—Called when the open state changes.
closeOnEscapebooleantrueWhether pressing Escape closes the dialog.
closeOnBackdropClickbooleantrueWhether clicking the backdrop closes the dialog.
childrenChildren—Content — typically DialogTrigger and DialogContent.

Methods

MethodDescription
openDialog()Opens the dialog. Emits onOpenChange(true).
closeDialog()Closes the dialog. Emits onOpenChange(false).
toggle()Toggles the open state. Emits onOpenChange.

Props — DialogTrigger

PropTypeDefaultDescription
dialogDialog—The Dialog instance this trigger controls.
childrenChildren—The trigger element content.
classstring—CSS class.
idstring—HTML id.

Props — DialogContent

PropTypeDefaultDescription
dialogDialog—The Dialog instance this content belongs to.
childrenChildren—Dialog body content.
classstring—CSS class.
idstring—HTML id. Falls back to an auto-generated id.
aria-labelstring—Accessible label. Use when no DialogTitle is present.
aria-labelledbystring—ID of the element that labels the dialog (e.g. a DialogTitle).
aria-describedbystring—ID of the element that describes the dialog (e.g. a DialogDescription).

Props — DialogTitle

PropTypeDefaultDescription
as"h1" | "h2" | "h3" | "h4" | "h5" | "h6""h2"The HTML heading element to render.
childrenChildren—Title text.
classstring—CSS class.
idstring—HTML id.

Props — DialogDescription

PropTypeDefaultDescription
childrenChildren—Description text.
classstring—CSS class.
idstring—HTML id.

Props — DialogClose

PropTypeDefaultDescription
dialogDialog—The Dialog instance this close button controls.
childrenChildren—Button content.
classstring—CSS class.
idstring—HTML id.

data-* attributes

AttributeElementWhen present
data-openDialogTriggerDialog is open
data-openDialogContent rootDialog is open

Always provide either aria-label or aria-labelledby on DialogContent. Point aria-labelledby at the id of your DialogTitle for best accessibility.

DialogContent calls trapFocus() and lockScroll() from @morphos/core when it mounts while open (and again whenever the dialog's open state changes). Focus cycles through the focusable elements inside the content element, and the page scroll lock is released automatically when the dialog closes or DialogContent unmounts.

Styling example

[data-morphos-backdrop] {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
}

.morphos-dialog-content {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  max-width: 28rem;
  padding: var(--morphos-space-4);
  border-radius: var(--morphos-radius-lg);
}

.morphos-dialog-close {
  position: absolute;
  top: var(--morphos-space-3);
  right: var(--morphos-space-3);
}

On this page