Dialog
A modal window for completing a short task or confirming a decision.
import { Dialog } from '@aksornproject/aksara-ui'Basic dialog
Dialog is a namespace of parts. Dialog.Trigger opens the dialog and Dialog.Close closes it. Both are unstyled, so pass a library Button through render.
Dialog.Popup draws the scrim, centres the surface and keeps it 24px from the overlay edges. Put a Dialog.Header, a Dialog.Body and a Dialog.Footer inside it.
<Dialog.Root>
<Dialog.Trigger render={<Button>Share report</Button>} />
<Dialog.Popup size="md">
<Dialog.Header title="Share report" subtitle="Term 1 science grades" />
<Dialog.Body>
<Typography className="text-color-text-primary">
Parents of the 32 students in M.1/2 get an email with a link to
their child’s report.
</Typography>
</Dialog.Body>
<Dialog.Footer className="flex flex-wrap items-center justify-end gap-spacing-8">
<Dialog.Close
render={
<Button variant="secondary-accent" size="lg">
Cancel
</Button>
}
/>
<Dialog.Close render={<Button size="lg">Share</Button>} />
</Dialog.Footer>
</Dialog.Popup>
</Dialog.Root>Sizes
Choose sm, md, lg or xl with size. lg is the default. Each size adapts to the available width and leaves at least 24px at the edges. full fills the overlay with no corner radius.
Sizes follow the overlay's width. If you use container, they adapt to that element rather than the browser window.
{SIZES.map((size) => (
<Dialog.Root key={size}>
<Dialog.Trigger
render={<Button variant="secondary-accent">{size}</Button>}
/>
<Dialog.Popup size={size}>
<Dialog.Header title={`A ${size} dialog`} />
<Dialog.Body>
<Typography className="text-color-text-primary">
The width comes from the surface modal width token for {size}.
</Typography>
</Dialog.Body>
<Dialog.Footer className="flex justify-end">
<Dialog.Close render={<Button size="lg">Done</Button>} />
</Dialog.Footer>
</Dialog.Popup>
</Dialog.Root>
))}Header
title names the dialog and clamps to two lines. subtitle adds a description below it. leadingIcon puts an icon before the title.
info adds a question button after the title. Its value is the tooltip content. actions sits between the title and the close button. extra adds a row under the title for badges or steps. showClose={false} removes the close button.
<Dialog.Root>
<Dialog.Trigger
render={<Button variant="secondary-accent">Class settings</Button>}
/>
<Dialog.Popup size="md">
<Dialog.Header
title="Class settings"
subtitle="M.1/2 science"
leadingIcon={<IconInterfaceEssentialSetting />}
info="Only teachers of this class can change these settings."
actions={
<Button variant="tertiary-accent" size="sm">
Reset
</Button>
}
extra={<Badge>32 students</Badge>}
/>
<Dialog.Body>
<Typography className="text-color-text-primary">
Choose who can join and when homework is due.
</Typography>
</Dialog.Body>
</Dialog.Popup>
</Dialog.Root>Scrolling body and footer
The header and the footer stay put. The body takes the rest of the height and scrolls once the popup reaches the overlay height. You do not set a height. This demo caps the popup with max-h-[480px] to show the scroll.
padding on Dialog.Body is default (24px) or flush (none). Both keep the 16px gap. Use flush for rows that touch the edges.
Dialog.Footer is one styled div with the surface, the top divider and the 24 by 16 padding. It never reorders, groups or stacks its children. Arrange the buttons in JSX and className. The JSX order is the DOM order, the Tab order and the screen order.
<Dialog.Root>
<Dialog.Trigger render={<Button>Filters</Button>} />
<Dialog.Popup size="md" className="max-h-[480px]">
<Dialog.Header
title="Filters"
subtitle="Grade"
extra={<Badge>{`${picked.length} selected`}</Badge>}
/>
<Dialog.Body padding="flush">
{GRADES.map((grade) => (
<div key={grade} className="px-spacing-24 py-spacing-12">
<Checkbox
label={grade}
checked={picked.includes(grade)}
onCheckedChange={(checked) =>
setPicked((all) =>
checked
? [...all, grade]
: all.filter((entry) => entry !== grade),
)
}
/>
</div>
))}
</Dialog.Body>
<Dialog.Footer className="flex flex-wrap items-center justify-end gap-spacing-12">
<div className="flex min-w-spacing-160 flex-1 items-center">
<Button
variant="link-accent"
size="lg"
disabled={picked.length === 0}
onClick={() => setPicked([])}
>
Clear all
</Button>
</div>
<div className="flex items-center gap-spacing-8">
<Dialog.Close
render={
<Button variant="secondary-accent" size="lg">
Cancel
</Button>
}
/>
<Dialog.Close render={<Button size="lg">Apply</Button>} />
</div>
</Dialog.Footer>
</Dialog.Popup>
</Dialog.Root>Alert dialog
Use Dialog.AlertRoot for a confirmation. It gives the popup role="alertdialog", is always modal and ignores presses outside. Only its buttons and Escape close it. Dialog.Trigger and the other parts work the same inside it.
<Dialog.AlertRoot>
<Dialog.Trigger
render={<Button color="destructive">Delete class</Button>}
/>
<Dialog.Popup size="sm">
<Dialog.Header title="Delete M.1/2?" />
<Dialog.Body>
<Typography className="text-color-text-primary">
Students will no longer see this class or its homework.
</Typography>
</Dialog.Body>
<Dialog.Footer className="flex flex-wrap items-center justify-end gap-spacing-8">
<Dialog.Close
render={
<Button variant="secondary-accent" size="lg">
Cancel
</Button>
}
/>
<Dialog.Close
render={
<Button color="destructive" size="lg">
Delete class
</Button>
}
/>
</Dialog.Footer>
</Dialog.Popup>
</Dialog.AlertRoot>Controlled
Pass open and onOpenChange to Dialog.Root to manage the state in your app, for example to close after a save.
The popup takes focus when it opens. Pass initialFocus to start in a field instead.
Science M.1/2
<Typography variant="body-medium" className="text-color-text-primary">
{name}
</Typography>
<Dialog.Root open={open} onOpenChange={openChange}>
<Dialog.Trigger
render={<Button variant="secondary-accent">Rename</Button>}
/>
<Dialog.Popup size="sm" initialFocus={field}>
<Dialog.Header title="Rename class" />
<Dialog.Body>
<TextInput
ref={field}
label="Class name"
value={draft}
onChange={(event) => setDraft(event.target.value)}
onKeyDown={(event) => {
if (event.key === 'Enter') save()
}}
/>
</Dialog.Body>
<Dialog.Footer className="flex flex-wrap items-center justify-end gap-spacing-8">
<Dialog.Close
render={
<Button variant="secondary-accent" size="lg">
Cancel
</Button>
}
/>
<Button size="lg" onClick={save}>
Save
</Button>
</Dialog.Footer>
</Dialog.Popup>
</Dialog.Root>Container
container renders the overlay inside an element instead of <body>. The overlay fills the element and the width tokens follow its width. Give the element a non-static position. Dialogs nested in a contained dialog stay inside the same element.
Menus, tooltips and nested dialogs opened inside a dialog portal into its layer, so they paint above the popup. A nested dialog draws its own backdrop.
<div
ref={stage}
className="relative flex h-[360px] w-full max-w-[810px] items-center justify-center overflow-hidden rounded-radius-radius-8 bg-color-background-surface"
>
<Dialog.Root>
<Dialog.Trigger render={<Button>Leave lesson</Button>} />
<Dialog.Popup size="sm" container={stage}>
<Dialog.Header title="Leave the lesson?" />
<Dialog.Body>
<Typography className="text-color-text-primary">
The lesson stops on the current slide.
</Typography>
</Dialog.Body>
<Dialog.Footer className="flex flex-wrap items-center justify-end gap-spacing-8">
<Dialog.Close
render={
<Button variant="secondary-accent" size="lg">
Keep teaching
</Button>
}
/>
<Dialog.Close render={<Button size="lg">Leave</Button>} />
</Dialog.Footer>
</Dialog.Popup>
</Dialog.Root>
</div>Accessibility
- The header
titlelabels the popup througharia-labelledby. Thesubtitledescribes it througharia-describedby. - A popup without a
Dialog.Headerneeds aDialog.Titleor anaria-label. Development builds log an error otherwise. - The popup takes focus when it opens, so the info tooltip does not cover the title for keyboard users. The next Tab reaches the info button. Focus returns to the trigger on close.
- The close button is named "Close" and the info button "More information". Pass
closeLabelandinfoLabelto translate them. Dialog.AlertRootgivesrole="alertdialog". Use it for confirmations.
API
Dialog.Root
Prop
Type
Dialog.AlertRoot
Prop
Type
Dialog.Trigger
Prop
Type
Dialog.Close
Prop
Type
Dialog.Popup
Use className to customise the popup. Dialog.Popup does not accept render.
Prop
Type
Dialog.Header
Prop
Type
Dialog.Title
Prop
Type
Dialog.Description
Prop
Type
Dialog.Body
Prop
Type
Dialog.Footer
DialogFooterProps is the props of a div, with a forwarded ref and className merged last. It adds no props of its own.