Skip to content

Dialog

Generic modal dialog primitive built on react-aria-components. Domain-agnostic and token-driven — a feature composes it into create/edit dialogs, a mobile sheet, etc. position/size variants cover a centered modal and a bottom sheet.

import { Dialog } from "@evolve-storefront/ui/dialog";
Design notes

Every overlay in ui is RAC for one reason: a modal that locks interaction by setting pointer-events: none on <body> makes any other library's portalled popup unclickable inside it, because the popup is a sibling of the dialog rather than a descendant. RAC's overlays know about each other, so a Select opened inside a Dialog behaves. Do not reintroduce a second overlay library.

Preview

DefaultOpen in Storybook ↗

Usage

The edit dialog, opened from its trigger. position="bottom" anchors it to the bottom edge as a mobile sheet, size="lg" widens it, and hideCloseButton drops the corner button where the footer's DialogClose is the way out: all three are controls here.

<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">{t("editList")}</Button>
</DialogTrigger>
<DialogContent closeLabel={t("close")}>
<DialogHeader>
<DialogTitle>{t("editListTitle")}</DialogTitle>
<DialogDescription>{t("editListDescription")}</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">{t("cancel")}</Button>
</DialogClose>
<Button onClick={save}>{t("save")}</Button>
</DialogFooter>
</DialogContent>
</Dialog>;

Props

PropTypeDescription
open?boolean

Controlled open state. Omit to let the dialog own it.

defaultOpen?boolean
onOpenChange?((open: boolean) => void)
children?ReactNode

Change the layout

Pass children to Dialog to arrange its parts yourself. Every part is exported from @evolve-storefront/ui/dialog.

<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">{t("editList")}</Button>
</DialogTrigger>
<DialogContent closeLabel={t("close")}>
<DialogHeader>
<DialogTitle>{t("editListTitle")}</DialogTitle>
<DialogDescription>{t("editListDescription")}</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">{t("cancel")}</Button>
</DialogClose>
<Button onClick={save}>{t("save")}</Button>
</DialogFooter>
</DialogContent>
</Dialog>;
DialogCloseCloses the dialog on click.
PropTypeDescription
asChild?boolean

Any other props go to the root element, like on a plain HTML element.

DialogContentThe modal panel.
PropTypeDescription
size?"sm" | "md" | "lg" | null
position?"center" | "bottom" | null
hideCloseButton?boolean

Hide the built-in top-right close button.

closeLabel?string
closeIcon?ReactNode

Replace the close glyph (rung 3). Defaults to the close icon.

overlayClassName?string
onClosed?(() => void)

Fires once the dialog has closed — the seam for resetting form state.

children?ReactNode

Any other props go to the root element, like on a plain HTML element.

DialogDescriptionRegisters itself so aria-describedby is only set when a description is actually rendered — a dangling reference is worse than none.

No props of its own.

DialogFooterThe row of actions.

No props of its own.

DialogHeaderGroups the title and the description at the top of the panel.

No props of its own.

DialogTitleslot="title" is what RAC reads to label the dialog.

No props of its own.

DialogTriggerOpens the dialog on click.
PropTypeDescription
asChild?boolean

Any other props go to the root element, like on a plain HTML element.

Examples

With Select
<DialogContent closeLabel={t("close")}>
<DialogHeader><DialogTitle>{t("addAddress")}</DialogTitle></DialogHeader>
<Select name="country" label={t("country")} placeholder={t("selectCountry")}>
<SelectItem id="nl">{t("countries.nl")}</SelectItem>
</Select>
<DialogFooter>…</DialogFooter>
</DialogContent>;
Open in Storybook ↗