Skip to content

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.

tsx
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

PropTypeDefaultDescription
localestringBCP 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
openbooleanWhether it is shown. With onOpenChange, a controlled dialog
defaultOpenbooleanfalseThe initial state of an uncontrolled dialog
onOpenChange(open: boolean) => voidCalled when it opens or closes
triggerReactElementThe element that opens it, wired up by Base UI. Optional: a controlled dialog opened elsewhere needs none
titleReactNodeThe heading. Rendered as the h2 that names the dialog
descriptionReactNodeA line under the title, and the dialog's accessible description
actionsReactNodeThe bottom row, end-aligned. DialogClose is what makes one of them dismiss
dividersbooleanfalseSeparates the sections with hairlines instead of space. Worth turning on the moment the body scrolls
showClosebooleantrueThe × in the corner. On by default, unlike most booleans here: a modal takes the page away, and the way out should be visible
closeLabelstringAccessible name of the × button
widthnumber | stringA hard cap overriding the one size implies. Numbers are pixels
fullWidthbooleantrueTakes the full width its size allows. On by default, the other way round from everywhere else: a dialog’s container is the viewport
fullScreenbooleanfalseFills the viewport edge to edge
modalboolean | 'trap-focus'trueWhether the page behind is taken away. 'trap-focus' keeps it scrollable and clickable while holding focus inside
dismissiblebooleantrueWhether Escape and an outside click close it. Turn it off only with actions that answer it
childrenReactNodeThe body: the only part that scrolls
classNamesNebaSlots<'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.

tsx
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.

tsx
<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

  • title and description are wired into aria-labelledby and aria-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.
  • showClose is 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.
  • locale decides the ×'s accessible name; closeLabel writes it out instead.

Released under the MIT License