Dialog
Modal dialogs with sticky header and footer, an unsaved-changes guard, stacking and keyboard submit.
Change the name and press Esc: the dialog asks before throwing your edit away. Press Cmd/Ctrl+Enter from the field to save.
Installation
npx shadcn@latest add @quireui/dialogUsage
<Dialog open={open} onOpenChange={setOpen} dirty={name !== original}>
<DialogTrigger render={<Button />}>Rename project</DialogTrigger>
<DialogContent size="sm">
<DialogForm onFormSubmit={save}>
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Only visible to people in this workspace.</DialogDescription>
</DialogHeader>
<DialogBody>{/* fields */}</DialogBody>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<Button type="submit" variant="primary">Save</Button>
</DialogFooter>
</DialogForm>
</DialogContent>
</Dialog>Sizes
size on DialogContent: sm, md (default), lg or full. For panels from the edge of the screen, use Sheet.
Sticky header and footer
Only DialogBody scrolls, so the title and the buttons stay in view on long content. It is a Scroll area: a slim scrollbar while scrolling and soft fades where there is more to read.
Unsaved changes
Pass dirty while there are unsaved edits. Esc, an outside click or the close button then show "Discard unsaved changes?" at the bottom of the dialog instead of closing. Focus goes to "Keep editing", and Esc again keeps editing. Set dirty back to false after saving. Change the wording with dirtyMessage.
Accessibility
It is a modal dialog, named by DialogTitle and described by DialogDescription, and the page behind it is hidden from screen readers while it is open. The close button is named "Close", and the unsaved-changes bar is an alert, so it is read out when it appears.
- The first field is focused when the dialog opens.
- Cmd/Ctrl+Enter submits the dialog's form from any field, including a textarea where Enter adds a new line.
DialogFormvalidates its fields with the same messages as Input.
Stacking
A dialog opened from inside another one stacks on top, and the one behind steps back so the depth is clear.
For a destructive confirm, use Alert dialog.
API reference
useCloseGuard
While dirty, closing (Esc, outside click, close button) is held back and the CloseGuardBar asks first. "Discard" closes through actionsRef, which the guard lets through.
Prop
Type
CloseGuardBar
The "Discard unsaved changes?" bar. Rendered by DialogContent and SheetContent.
Dialog
Prop
Type
Also takes every prop of Base UI's Dialog.Root.
DialogTrigger
Also takes every prop of Base UI's Dialog.Trigger.
DialogClose
Also takes every prop of Base UI's Dialog.Close.
DialogContent
Prop
Type
Also takes every prop of Base UI's Dialog.Popup. Style it with [data-slot="dialog"].
DialogForm
Wrap header, body and footer to submit with the footer button or Cmd/Ctrl+Enter.
DialogHeader
Also takes every <div> attribute.
DialogBody
The only part that scrolls, so the title and the buttons stay in view.
DialogFooter
Also takes every <div> attribute.
DialogTitle
Also takes every prop of Base UI's Dialog.Title.
DialogDescription
Also takes every prop of Base UI's Dialog.Description.