Manual UI Templates Dialog

Dialog

elements man ui/templates/dialog

A modal window over the page that traps focus until it closes.

Example
import { Dialog, DialogFooter, DialogClose } from "@elements/ui/dialog"; import { Button } from "@elements/ui/button"; <Dialog title="Edit profile" description="Make changes, then save."> <Button slot="trigger">Edit profile</Button> <input value={form.name}> <DialogFooter> <DialogClose><Button>Cancel</Button></DialogClose> <Button variant="primary" onclick={() => save(form)}>Save</Button> </DialogFooter> </Dialog> <!-- controlled: open is two-way --> <Dialog open={state.editing} title="Edit profile">...</Dialog>

Parts

Part What it is
Dialog The root: owns open, renders the trigger slot and, while open, the <dialog> overlay and panel.
DialogTitle An <h2 class="dialog-title">; names the dialog (aria-labelledby). Use it instead of the title attribute for rich content.
DialogDescription A <p class="dialog-description">; describes the dialog (aria-describedby).
DialogHeader Stacks a title and description.
DialogBody A column for the content between header and footer.
DialogFooter Right-aligned actions; stacked full width on phones. closeButton appends an outline Close button.
DialogClose Wraps any element; clicking it closes this dialog (not an outer one).

Attributes

Dialog

Attribute Type Default Description
open boolean (two-way) false Whether the dialog is open. Bind it (open={state.x}) to control it; the dialog writes false back when it closes itself. Omit it and the trigger slot opens it.
title string "" Renders a DialogTitle in a header and names the dialog.
description string "" Renders a DialogDescription under the title.
label string "" aria-label for a dialog with no visible title.
modal boolean | "trap-focus" true true: backdrop, inert page, focus trap, scroll lock. "trap-focus": focus is trapped but the page scrolls and takes clicks. false: the page stays usable and focus leaving closes it.
dismissOnEscape boolean true Escape closes the dialog.
dismissOnOutside boolean true A pointer press outside the panel closes the dialog.
closeButton boolean true Shows the X button in the corner.
closeLabel string "Close" The X button's accessible name. Set it in the page's language.
initialFocus string | ((type: DialogInteraction) => HTMLElement | string | boolean | null | void) "" CSS selector, inside the dialog, of the element to focus on open. Default: [autofocus], else the first focusable element that is not a link, else the panel. A function gets how the dialog opened (mouse, touch, pen, keyboard, or "" when the caller opened it) and returns an element or selector, true or null for the default, or false (or nothing) to leave focus alone.
returnFocus boolean true Return focus to the trigger (or whatever had focus) when the dialog closes. Opened from a menu item, focus returns to the menu's button.
finalFocus string | ((type: DialogInteraction) => HTMLElement | string | boolean | null | void) "" CSS selector, anywhere on the page, of the element to focus on close instead of the trigger. A function gets how the dialog closed and returns an element or selector, true or null for the default, or false (or nothing) to leave focus alone.
triggers string "" CSS selector, anywhere on the page, of buttons outside the dialog that open it through a controlled open. Each says it opens a dialog, and only the one that opened it is marked open (aria-expanded, data-popup-open); focus returns to it.
size "sm" | "md" | "lg" | "xl" | "full" "md" Panel max width. md is the theme's --dialog-width.
scroll "outside" | "inside" "outside" outside: a tall dialog scrolls with the overlay. inside: the panel fits the viewport, the header and footer stay, and DialogBody scrolls.
class string "" Extra classes on the panel.
onOpenChange (open: boolean, reason?: DialogChangeReason) => void () => {} Called when the dialog opens or closes itself, with why: trigger-press, escape-key, outside-press, close-press, focus-out, close-watcher (a platform close request, such as Android's back gesture) or none. Not called for changes the caller makes.
onOpenChangeComplete (open: boolean) => void () => {} Called after the enter animation (true) and after the exit, once closed (false).
onEscapeKeyDown (e: KeyboardEvent) => void () => {} Called on Escape before closing; e.preventDefault() keeps it open, and e.allowPropagation() lets the Escape reach the page's own key handlers after it closes the dialog.
onPointerDownOutside (e: PointerEvent) => void () => {} Called on a press outside before closing; e.preventDefault() keeps it open.
onInteractOutside (e: Event) => void () => {} Called on a press outside, or focus moving outside a non-modal dialog, before closing; e.preventDefault() keeps it open.
onOpenAutoFocus (e: Event) => void () => {} Called before focus moves in on open; e.preventDefault() leaves focus to the handler.
onCloseAutoFocus (e: Event) => void () => {} Called before focus returns on close; e.preventDefault() leaves focus to the handler.

DialogFooter

Attribute Type Default Description
closeButton boolean false Appends an outline Close button that closes the dialog.
closeLabel string "Close" The footer Close button's text. Set it in the page's language.

DialogTitle, DialogDescription, DialogHeader, DialogBody, DialogFooter

Attribute Type Default Description
class string "" Extra classes.

Slots

Slot Content
trigger The element that opens the dialog, usually a <Button>. It gets aria-haspopup, aria-expanded, aria-controls and data-state.
default The dialog's content, below the header.

Keyboard

Keys Action
Space / Enter On the trigger: opens the dialog, or closes it when open (a non-modal dialog leaves its trigger reachable). On the close button or a DialogClose button: closes it.
Tab Moves to the next focusable element inside; wraps from the last to the first.
Shift + Tab Moves to the previous focusable element; wraps from the first to the last.
Escape Closes the dialog (unless dismissOnEscape is false or onEscapeKeyDown prevents it). In nested dialogs, only the top one.

Data attributes

Attribute Values On Description
data-state "open" | "closed" trigger, .dialog-overlay, .dialog closed during the exit animation.
data-modal "true" | "trap-focus" | "false" .dialog-overlay
data-nested present .dialog The dialog is inside another dialog.
data-nested-dialog-open present .dialog A dialog inside this one is open.
--nested-dialogs number .dialog How many dialogs are open inside this one.
data-layer stack depth .dialog Set while the dialog is a dismiss layer.
data-scroll-locked present <body> While any modal dialog is open.

Accessibility

  • The overlay is a native <dialog> opened with showModal(): it has role dialog, sits in the top layer, and the rest of the page is inert to pointer, focus and assistive technology.
  • aria-modal="true" (modal only), aria-labelledby (the title) or aria-label (label), and aria-describedby (the description, omitted without one) are set on the <dialog>.
  • Focus moves into the dialog on open, is trapped while modal, and returns to the trigger on close; when the dialog was opened from elsewhere it returns to the element that had focus.
  • The trigger gets aria-haspopup="dialog", aria-expanded and aria-controls.
  • The close button is an icon button with aria-label="Close".

See also