Dialog
A modal sheet that covers the page until it is answered. Use it to confirm an action or to take input that has to interrupt the flow.
import { Button, Dialog, DialogClose } from 'neba';
<Dialog
trigger={<Button color="danger">Delete workspace</Button>}
title="Delete this workspace?"
description="Every project, deploy and log inside it goes with it."
actions={<DialogClose render={<Button color="danger">Delete</Button>} />}
>
This cannot be undone.
</Dialog>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| locale | string | — | BCP 47 tag naming the × in that language. Unsupported tags fall back to English |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | The type scale, the padding, and how wide the sheet may get. One axis rather than a second scale spelled maxWidth |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. The sheet is never dyed, so it reaches the edge and the focus ring |
| densityshared | 'default' | 'compact' | 'default' | Padding only |
| open | boolean | — | Whether it is shown. With onOpenChange, a controlled dialog |
| defaultOpen | boolean | false | The initial state of an uncontrolled dialog |
| onOpenChange | (open: boolean) => void | — | Called when it opens or closes |
| trigger | ReactElement | — | The element that opens it, wired up by Base UI. Optional: a controlled dialog opened elsewhere needs none |
| title | ReactNode | — | The heading. Rendered as the h2 that names the dialog |
| description | ReactNode | — | A line under the title, and the dialog's accessible description |
| actions | ReactNode | — | The bottom row, end-aligned. DialogClose is what makes one of them dismiss |
| dividers | boolean | false | Separates the sections with hairlines instead of space. Worth turning on the moment the body scrolls |
| showClose | boolean | true | The × in the corner. On by default, unlike most booleans here: a modal takes the page away, and the way out should be visible |
| closeLabel | string | — | Accessible name of the × button |
| width | number | string | — | A hard cap overriding the one size implies. Numbers are pixels |
| fullWidth | boolean | true | Takes the full width its size allows. On by default, the other way round from everywhere else: a dialog’s container is the viewport |
| fullScreen | boolean | false | Fills the viewport edge to edge |
| modal | boolean | 'trap-focus' | true | Whether the page behind is taken away. 'trap-focus' keeps it scrollable and clickable while holding focus inside |
| dismissible | boolean | true | Whether Escape and an outside click close it. Turn it off only with actions that answer it |
| children | ReactNode | — | The body: the only part that scrolls |
| classNames | NebaSlots<'backdrop' | 'viewport' | 'title' | 'description' | 'close' | 'body' | 'actions'> | — | Class names for the parts behind the root. The root itself is className, so there is no root key |
Native <div> attributes pass through to the popup. Only color, title and children are excluded, since the table above spells them differently.
There is no variant and no elevation: a modal always carries a level-3 shadow.
Examples
size and width
size sets the type scale and the padding along with the sheet's maximum width. When you need to break that pairing (small type on a wide sheet for a table or a diff), set a length in width.
dividers
With a long body, only the body scrolls; the heading and the actions stay put. dividers rules those boundaries, which is what shows the header did not scroll away.
dismissible
dismissible={false} blocks both Escape and the click outside. Turn it off only when actions holds a button that answers the dialog: otherwise there is no way out.
DialogClose
Closes the dialog from a button without owning the open state yourself. Use render to put the control you want inside it.
actions={
<>
<DialogClose render={<Button variant="text" color="secondary">Cancel</Button>} />
<DialogClose render={<Button color="danger">Delete</Button>} />
</>
}classNames
className lands on the popup: the sheet itself, which is what a caller means by "the dialog". Everything around and inside it is reached through classNames.
<Dialog
title="Delete this?"
classNames={{ backdrop: 'backdrop-blur-none', actions: 'justify-between' }}
/>The slots are backdrop, viewport, title, description, close, body and actions. The first two are the ones with no other way in: both render at the end of <body>, outside the popup, so no selector written against the sheet finds them. See prop conventions for how a class name you pass resolves against the component's own.
Accessibility
titleanddescriptionare wired intoaria-labelledbyandaria-describedby. The title renders as a real<h2>.- The focus trap, the scroll lock, the inert page behind, and focus returning to the trigger on close are all handled.
showCloseis on by default. The way out of a modal should always be visible, and it is also what a touch screen reader uses to escape the popup.localedecides the ×'s accessible name;closeLabelwrites it out instead.