Dialog

A modal window, built on the native <dialog> element — so the focus trap, background inerting, Escape handling and top-layer stacking come from the platform rather than from JavaScript.

src/components/ui/dialog.tsx

About

From the component's own source, where it was written.

A modal window, built on the native <dialog> element.

That gets the focus trap, background inerting, Escape handling and top-layer stacking from the platform rather than from JavaScript — the parts a hand-made modal usually gets subtly wrong.

Install

npx astralyx-ui add dialog

Usage

import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogBody,
} from '@/components/ui/dialog'

<Dialog>
  <DialogTrigger asChild>
    <Button>Open</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Rename project</DialogTitle>
    </DialogHeader>
    <DialogBody>…</DialogBody>
  </DialogContent>
</Dialog>

Composer

Change the props and the source below follows.

Rename project

This name is visible to everyone in the workspace.

Props
tsx
<Dialog>
  <DialogTrigger asChild>
    <Button>Open</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Rename project</DialogTitle>
      <DialogDescription>This is visible to everyone.</DialogDescription>
    </DialogHeader>
    <DialogBody>…</DialogBody>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button>Save</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Examples

Default

Not dismissable, no close button

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Dialog

A modal window, built on the native <dialog> element.

PropTypeDefaultDescription
openboolean—Controlled and uncontrolled open state, on Dialog.
defaultOpenboolean—Controlled and uncontrolled open state, on Dialog.
onOpenChange(open: boolean) => void—Fires on every change, including Escape and backdrop dismissal.
dismissablebooleantrueOn the root: false refuses Escape and backdrop presses together, for a dialog whose action is in flight. Its own buttons still close it, and a browser that closes the element anyway — repeated Escapes can skip cancel — finds it shown again while open stays true.
childrenReactNoderequired—

DialogTrigger

Extends ComponentProps<'button'>.

PropTypeDefaultDescription
asChildbooleanfalseRender your own element as the trigger instead of a bare button.

DialogClose

Extends ComponentProps<'button'>.

PropTypeDefaultDescription
asChildbooleanfalse—

DialogContent

Extends ComponentProps<'dialog'>.

PropTypeDefaultDescription
showClosebooleantrueCorner close button.
dismissablebooleantrueOn DialogContent: whether a backdrop press closes the dialog. Escape still works.
size'sm' | 'default' | 'lg' | 'xl''default'Maximum width of the panel.
closeLabelstring'Close'Accessible name for the close button.

DialogHeader

Extends ComponentProps<'div'>.

DialogTitle

Extends ComponentProps<'h2'>.

DialogDescription

Extends ComponentProps<'p'>.

DialogBody

Extends ComponentProps<'div'>.

DialogFooter

Extends ComponentProps<'div'>.

Behaviour

TopicDetailDescription
closed statenot-open:hiddenA display utility in className (flex, grid) used to beat the UA's display: none and leave a closed dialog drawn in the page. The closed rule is re-applied above it.