Skip to content

Dialog

A modal task or confirmation. Title required; focus is trapped and returns to the trigger.

  • Free core, MIT
  • Version 2.0.0-rc.1
  • APG pattern: Dialog (modal); Alert dialog

Examples

These are the real React components. Use them with a mouse, touch or keyboard, and switch the theme and density for every example at once.

Default

Show the code

Default, code

<DialogTrigger>
  <Button>Invite member</Button>
  <Dialog
    {...args}
    description="They will get an email with a link to join Northwind."
    footer={<><Button variant="outline" slot="close">Cancel</Button><Button slot="close">Send invite</Button></>}
  >
    <TextField label="Email" type="email" autoComplete="email" />
  </Dialog>
</DialogTrigger>

Open

Show the code

Open, code

<DialogTrigger defaultOpen>
  <Button>Invite member</Button>
  <Dialog
    {...args}
    description="They will get an email with a link to join Northwind."
    footer={<><Button variant="outline" slot="close">Cancel</Button><Button slot="close">Send invite</Button></>}
  >
    <TextField label="Email" type="email" autoComplete="email" />
  </Dialog>
</DialogTrigger>

Alert dialog

Show the code

Alert dialog, code

<DialogTrigger defaultOpen>
  <Button variant="destructive">Remove Tom Ellis</Button>
  <Dialog
    role="alertdialog"
    size="sm"
    title="Remove Tom Ellis?"
    description="Tom will lose access to Northwind straight away. You can invite him again later."
    footer={<><Button variant="outline" slot="close" autoFocus>Cancel</Button><Button variant="destructive" slot="close">Remove member</Button></>}
  />
</DialogTrigger>

Sizes

Show the code

Sizes, code

<div className="flex flex-wrap gap-gap-md">
  {(['xs', 'sm', 'md', 'lg', 'full'] as const).map((s) => (
    <DialogTrigger key={s}>
      <Button variant="outline">Open {s}</Button>
      <Dialog size={s} title={`Dialog, size ${s}`} description="Sizes change the maximum width only." footer={<Button slot="close">Done</Button>} />
    </DialogTrigger>
  ))}
</div>

Phone alert

Show the code

Phone alert, code

<DialogTrigger defaultOpen>
  <Button variant="destructive">Delete draft</Button>
  <Dialog
    role="alertdialog"
    size="xs"
    title="Delete this draft?"
    description="You can't undo this."
    footer={<><Button variant="outline" slot="close" autoFocus>Cancel</Button><Button variant="destructive" slot="close">Delete</Button></>}
  />
</DialogTrigger>

Wizard footer

Show the code

Wizard footer, code

<DialogTrigger defaultOpen>
  <Button>Set up your team</Button>
  <Dialog
    title="Invite your team"
    description="Step 2 of 3. You can add more people later."
    footerStart={<Button variant="ghost">Back</Button>}
    footer={<><Button variant="outline" slot="close">Skip</Button><Button>Continue</Button></>}
  >
    <TextField label="Email" type="email" autoComplete="email" />
  </Dialog>
</DialogTrigger>

Scrolling body

Show the code

Scrolling body, code

<DialogTrigger defaultOpen>
  <Button>Read the terms</Button>
  <Dialog
    scrollBody
    title="Terms of service"
    description="Last updated 1 October 2026."
    footer={<><Button variant="outline" slot="close">Decline</Button><Button slot="close">Accept</Button></>}
  >
    {Array.from({ length: 12 }, (_, i) => (
      <p key={i} className="text-body-md">{i + 1}. These terms cover how you use Northwind, what we do with your data and how either of us can end the agreement. Read each section before you accept.</p>
    ))}
  </Dialog>
</DialogTrigger>

Tokens

The 14 design tokens Dialog reads, with the value each has in the theme and density chosen above. Change a token and the component follows, in Figma and in code.

Show all 14 tokens
Tokens Dialog reads
TokenValue nowCSS variable
color.border.subtle…--sd-color-border-subtle
color.text.secondary…--sd-color-text-secondary
dialog.bg…--sd-dialog-bg
dialog.fg…--sd-dialog-fg
dialog.padding…--sd-dialog-padding
dialog.radius…--sd-dialog-radius
dialog.scrim…--sd-dialog-scrim
elevation.3…--sd-elevation-3
layer.modal…--sd-layer-modal
radius.none…--sd-radius-none
spacing.gap.md…--sd-spacing-gap-md
spacing.stack.lg…--sd-spacing-stack-lg
spacing.stack.md…--sd-spacing-stack-md
spacing.stack.xs…--sd-spacing-stack-xs

Install

The npm package isn’t published yet. When it is, this is the install line; until then, this page runs the same build of the package (from commit fa3abf8).

Install

npm i @superduperui/react @superduperui/tokens

Import

import { Dialog, DialogTrigger } from "@superduperui/react"

Props

Dialog also takes the props of the React Aria component it’s built on.

Dialog props
PropTypeWhat it does
titlerequiredstringRequired: names the dialog (aria-labelledby) and is its visible heading.
descriptionReactNodeOptional supporting sentence, linked by aria-describedby.
childrenReactNode | ((opts: { close: () => void }) => ReactNode)Body content, or a render function that receives `close`.
footerReactNodeActions, right-aligned. Buttons with slot="close" close the dialog.
footerStartReactNodeA leading action at the start of the footer, apart from the others: Back in a wizard, a secondary link.
size'xs' | 'sm' | 'md' | 'lg' | 'full'Default 'md'xs 20rem (the 320px phone alert), sm 24rem, md 32rem (default), lg 48rem, full: the whole viewport.
scrollBodybooleanDefault falseLong content: the title and footer stay in view and only the body scrolls, between two rules. The body is a focusable region named by the title, so keyboard users can scroll it.
role'dialog' | 'alertdialog'Default 'dialog'"alertdialog" for confirmations: not dismissed by clicking outside; focus goes to the least destructive action you mark autoFocus.
showCloseButtonbooleanShow the close button (default true for dialog, false for alertdialog).
isOpenbooleanControlled open state (when not inside a DialogTrigger).
onOpenChange(isOpen: boolean) => void
classNamestring

When to use it

Use it for

  • A short task that must finish before going back (invite someone)
  • Confirming a destructive action (role="alertdialog")

Use something else for

  • Long forms: use a page
  • Non-blocking messages: use Toast

Accessibility

Screen readers announce "Invite member, dialog, They will get an email…".

WCAG 2.2 AAA criteria the component meets in Comfortable density: 1.4.6, 1.4.9, 2.1.3, 2.3.2, 2.4.13, 2.5.5, 2.5.6, 2.3.3.

Compact density meets AA (2.5.8, 24px targets), not AAA.

What your product still has to do

  • Put autoFocus on the least destructive action in alert dialogs

Shared patterns it ships with: 2.4.12 (scroll-padding variables); 3.3.6 (confirm destructive actions).

This release’s test results

Test results for Dialog in this release
axe-core (WCAG A, AA, AAA and best practice)140 runs across every story, theme, density and width: 0 violations
Rendered text contrast315 text runs: 0 below 7:1, lowest 8.43:1
Target size50 targets: 0 under 44px in Comfortable
Focus appearance (2.4.13)90 focus stops in 3 browser engines: all pass
Keyboard scriptChromium 6 of 6, Firefox 6 of 6, WebKit 6 of 6

Manual passes with VoiceOver and NVDA haven’t been done yet.

Keyboard

Each step is scripted and run in Chromium, Firefox and WebKit.

Dialog keyboard steps
KeyWhat happens
TabTab reaches the trigger
EnterEnter opens the dialog and focus moves inside it
TabTab moves to the first field
TabTab cycles Cancel, Send invite, Close and wraps: focus never leaves the dialog
Shift+TabShift+Tab also stays inside
EscapeEsc closes (a second Esc if a tooltip was open) and focus returns to the trigger

In Figma

The Figma component is Dialog, with Size (Small, Medium, Large, Full, Mobile); Role (Dialog, Alert dialog); Body (Default, Scrolling). The Figma library isn’t published yet.

Figma to code: Size → size (Small = "sm", Medium = "md", Large = "lg", Full = "full", Mobile = "xs"); Role → role (Dialog = "dialog", Alert dialog = "alertdialog"); Title → title; Description → description when Show description; Content → children when Show content; Body → scrollBody (Default = false, Scrolling = true); Actions → footer when Show footer; nested Leading action → footerStart.