Dialog
A modal overlay that interrupts the user to show focused content or request a decision.
Usage
import { Dialog } from '@acronis-platform/ui-react';Dialog is the sanctioned way to build a dialog. A single variant prop
selects one of seven canned use-cases — default, rename, save changes,
reset password, discard changes, accept, read-only — each with its own
title, body copy, and footer buttons, so product code doesn't hand-assemble the
same header/body/footer boilerplate every time. children overrides the body
slot; hasLoading shows a spinner overlay across the body + footer. It's built
internally on Base UI's Dialog primitive (focus trap, scroll lock,
Esc/outside-press dismissal, and ARIA come from Base UI), but that primitive
is not itself exported — if none of the seven variants fit your use case, raise
it with the design system team rather than reaching for a lower-level API.
An eighth wide variant is a legacy escape hatch — no Figma preset, meant to
be paired with a fully custom title/children/footer — kept only for
backward compatibility with pre-existing wider dialogs; prefer one of the
seven canned variants for new work.
Examples
A canned use-case, controlled open state. onPrimaryAction fires when the
primary footer button is clicked — the dialog doesn't close on its own, so
close it yourself once the action completes. (read-only is the one exception:
its single "Done" button both fires onPrimaryAction and dismisses the
dialog.)
const [open, setOpen] = useState(false);
<Dialog
variant="discard changes"
open={open}
onOpenChange={setOpen}
onPrimaryAction={() => {
discardChanges();
setOpen(false);
}}
/>;Override the body slot while keeping the header/footer chrome:
<Dialog variant="default" defaultOpen>
<p>Any custom body content.</p>
</Dialog>Busy state — the body and footer become inert (unfocusable, unclickable) and
the popup gets aria-busy="true" while hasLoading is set; loadingLabel
overrides the overlay's accessible name:
<Dialog variant="save changes" hasLoading loadingLabel="Enregistrement…" open />Localize the canned copy — title, secondaryLabel, primaryLabel,
closeLabel, and loadingLabel all override the variant's default English
string:
<Dialog
variant="discard changes"
title="Modifications non enregistrées"
secondaryLabel="Retour"
primaryLabel="Confirmer"
closeLabel="Fermer"
open
/>rename, discard changes, and accept embed a generic "object name"
placeholder in their canned title/body. Pass objectName to interpolate the
real name (e.g. a file being renamed) instead of overriding title/children
by hand:
<Dialog variant="rename" objectName="Q3 Report.xlsx" open onOpenChange={setOpen} />The rename variant's text field accessible name is also overridable via
objectNameLabel (default 'Object name'):
<Dialog variant="rename" objectNameLabel="Nom de l'objet" open onOpenChange={setOpen} />The popup always keeps a 48px minimum margin from every viewport edge — each
size is capped at calc(100dvw - 96px) wide and the popup at
calc(100dvh - 96px) tall — so a narrow or short viewport shrinks the dialog
rather than letting it run off-screen. Within that clamp the header and footer
hold their token heights and never compress; the body absorbs the remaining
space and scrolls on its own, so don't add another vertical scroll container
around children — that would create two competing vertical scrollbars.
Short body content stays vertically centered, while content taller than the
available space scrolls with the top edge always reachable. The popup's width
is likewise fixed to whichever size is active, never content-driven — wide,
non-wrapping content (e.g. a table that doesn't wrap) scrolls horizontally
within the body instead of widening the popup. A component with its own
horizontal-only scroll region (e.g. DataTable) composes fine here since it
doesn't compete with the body's vertical scroll:
<Dialog variant="read-only" open>
{longLegalText.map((paragraph) => (
<p key={paragraph}>{paragraph}</p>
))}
</Dialog>When the dialog renders inside an isolated container (e.g. a shadow root), pass a
portalContainer so the popup inherits that scope's styles:
<Dialog portalContainer={mountNode}>{/* … */}</Dialog>keepMounted leaves the popup markup in the DOM while the dialog is closed
(hidden, and not exposed as a dialog to assistive tech) instead of unmounting
it — reach for it when the body holds state or an expensive child you don't
want torn down and rebuilt on every open:
<Dialog keepMounted open={open} onOpenChange={setOpen}>
<ExpensiveForm />
</Dialog>Hide the header and/or footer chrome for a body-only dialog. Both default to
true; this is beyond the strict Figma contract (all seven canned variants
always show both). When the header is hidden, the title still renders
off-screen so the dialog keeps an accessible name:
<Dialog hasHeader={false} open>
<p>Body-only content — no title bar, no close button.</p>
</Dialog>Legacy wide variant — a free-form footer for pre-existing wider dialogs that
don't fit one of the seven canned use-cases:
import { Button, DialogClose } from '@acronis-platform/ui-react';
<Dialog
variant="wide"
size="large"
title="Configure discovery agent"
open
footer={
<>
<DialogClose render={<Button variant="ghost">Cancel</Button>} />
<Button>Configure</Button>
</>
}
>
<p>Any custom body content.</p>
</Dialog>API Reference
Prop
Type