Dialog

A modal overlay that interrupts the page for a focused task, built on Base UI's dialog primitive.

Import

import { Dialog } from '@oztix/roadie-components/dialog'

Examples

Each example leads with an IconTile (size='2xl' shape='circle', its intent inherited from the dialog) centred above the title — Dialog.Header handles the centring.

Default

A trigger opens a centred modal over a dimmed backdrop. Dialog.Content collapses the underlying Portal, Backdrop, Viewport, and Popup into a single element.

<Dialog>
  <Dialog.Trigger render={<Button>Open dialog</Button>} />
  <Dialog.Content>
    <Dialog.Header>
      <IconTile size='2xl' shape='circle'>
        <Envelope weight='duotone' />
      </IconTile>
      <Dialog.Title>Subscribe</Dialog.Title>
      <Dialog.Description>Get product updates in your inbox.</Dialog.Description>
    </Dialog.Header>
    <Dialog.Footer>
      <Dialog.Close render={<Button>Cancel</Button>} />
      <Button intent='accent' emphasis='strong'>Subscribe</Button>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog>

Sizes

Use the size prop on Dialog.Content to set the maximum width. Defaults to md.

<div className='flex flex-wrap gap-3'>
  <Dialog>
    <Dialog.Trigger render={<Button>Small</Button>} />
    <Dialog.Content size='sm'>
      <Dialog.Header>
        <Dialog.Close render={<IconButton aria-label='Close' emphasis='subtle' size='sm' className='absolute left-0 top-0'><X weight='bold' /></IconButton>} />
        <IconTile size='2xl' shape='circle'>
          <Info weight='duotone' />
        </IconTile>
        <Dialog.Title>Small dialog</Dialog.Title>
        <Dialog.Description>max-w-sm</Dialog.Description>
      </Dialog.Header>
    </Dialog.Content>
  </Dialog>
  <Dialog>
    <Dialog.Trigger render={<Button>Medium</Button>} />
    <Dialog.Content size='md'>
      <Dialog.Header>
        <Dialog.Close render={<IconButton aria-label='Close' emphasis='subtle' size='sm' className='absolute left-0 top-0'><X weight='bold' /></IconButton>} />
        <IconTile size='2xl' shape='circle'>
          <Info weight='duotone' />
        </IconTile>
        <Dialog.Title>Medium dialog</Dialog.Title>
        <Dialog.Description>max-w-md (default)</Dialog.Description>
      </Dialog.Header>
    </Dialog.Content>
  </Dialog>
  <Dialog>
    <Dialog.Trigger render={<Button>Large</Button>} />
    <Dialog.Content size='lg'>
      <Dialog.Header>
        <Dialog.Close render={<IconButton aria-label='Close' emphasis='subtle' size='sm' className='absolute left-0 top-0'><X weight='bold' /></IconButton>} />
        <IconTile size='2xl' shape='circle'>
          <Info weight='duotone' />
        </IconTile>
        <Dialog.Title>Large dialog</Dialog.Title>
        <Dialog.Description>max-w-lg</Dialog.Description>
      </Dialog.Header>
    </Dialog.Content>
  </Dialog>
</div>

Intents

Portaled popups sit outside the trigger's CSS cascade, so set the colour context directly on Dialog.Content with the intent prop. Each intent fits a different moment — info and success for informational modals (dismiss-only, with a header close), warning and danger for caution and destructive confirmations, and accent to promote an action.

<div className='flex flex-wrap gap-3'>
  {/* Info — informational, dismiss-only */}
  <Dialog>
    <Dialog.Trigger render={<IconButton aria-label='Release notes' emphasis='subtle'><Info weight='bold' /></IconButton>} />
    <Dialog.Content intent='info' size='sm'>
      <Dialog.Header>
        <Dialog.Close render={<IconButton aria-label='Close' emphasis='subtle' size='sm' className='absolute left-0 top-0'><X weight='bold' /></IconButton>} />
        <IconTile size='2xl' shape='circle'>
          <Info weight='duotone' />
        </IconTile>
        <Dialog.Title>What's new in 2.0</Dialog.Title>
        <Dialog.Description>Saved views, faster search, and a redesigned inbox.</Dialog.Description>
      </Dialog.Header>
    </Dialog.Content>
  </Dialog>

  {/* Success — confirmation, dismiss-only */}
  <Dialog>
    <Dialog.Trigger render={<Button>Finish setup</Button>} />
    <Dialog.Content intent='success' size='sm'>
      <Dialog.Header>
        <Dialog.Close render={<IconButton aria-label='Close' emphasis='subtle' size='sm' className='absolute left-0 top-0'><X weight='bold' /></IconButton>} />
        <IconTile size='2xl' shape='circle'>
          <CheckCircle weight='duotone' />
        </IconTile>
        <Dialog.Title>You're all set</Dialog.Title>
        <Dialog.Description>Your workspace is ready to go.</Dialog.Description>
      </Dialog.Header>
    </Dialog.Content>
  </Dialog>

  {/* Warning — caution confirm */}
  <Dialog role='alertdialog'>
    <Dialog.Trigger render={<Button>Save and exit</Button>} />
    <Dialog.Content intent='warning' size='sm'>
      <Dialog.Header>
        <IconTile size='2xl' shape='circle'>
          <Warning weight='duotone' />
        </IconTile>
        <Dialog.Title>Discard changes?</Dialog.Title>
        <Dialog.Description>You have unsaved edits that will be lost.</Dialog.Description>
      </Dialog.Header>
      <Dialog.Footer>
        <Dialog.Close render={<Button>Keep editing</Button>} />
        <Dialog.Close render={<Button intent='warning' emphasis='strong'>Discard</Button>} />
      </Dialog.Footer>
    </Dialog.Content>
  </Dialog>

  {/* Danger — destructive confirm */}
  <Dialog role='alertdialog'>
    <Dialog.Trigger render={<Button intent='danger'>Remove member</Button>} />
    <Dialog.Content intent='danger' size='sm'>
      <Dialog.Header>
        <IconTile size='2xl' shape='circle'>
          <Trash weight='duotone' />
        </IconTile>
        <Dialog.Title>Remove Jordan?</Dialog.Title>
        <Dialog.Description>They lose access to this workspace immediately.</Dialog.Description>
      </Dialog.Header>
      <Dialog.Footer>
        <Dialog.Close render={<Button>Cancel</Button>} />
        <Dialog.Close render={<Button intent='danger' emphasis='strong'>Remove</Button>} />
      </Dialog.Footer>
    </Dialog.Content>
  </Dialog>

  {/* Accent — promote an upgrade */}
  <Dialog>
    <Dialog.Trigger render={<Button intent='accent' emphasis='strong'>Upgrade</Button>} />
    <Dialog.Content intent='accent' size='sm'>
      <Dialog.Header>
        <IconTile size='2xl' shape='circle'>
          <Star weight='duotone' />
        </IconTile>
        <Dialog.Title>Upgrade to Pro</Dialog.Title>
        <Dialog.Description>Unlock unlimited projects and priority support.</Dialog.Description>
      </Dialog.Header>
      <Dialog.Footer>
        <Dialog.Close render={<Button>Maybe later</Button>} />
        <Button intent='accent' emphasis='strong'>Upgrade now</Button>
      </Dialog.Footer>
    </Dialog.Content>
  </Dialog>
</div>

Alert dialog

For a blocking message that demands a response — a session timeout, a cart-expiry warning, an irreversible confirmation — set role='alertdialog' on the root. This exposes the surface as role="alertdialog" and raises the backdrop and surface to the z-alert tier so the dialog stacks above an already-open dialog or drawer. The default role='dialog' stays on the z-overlay / z-modal tiers.

<Dialog role='alertdialog' open={expired} onOpenChange={onClose}>
<Dialog.Content size='sm'>
<Dialog.Header>
<Dialog.Title>Your cart has expired</Dialog.Title>
</Dialog.Header>
<Dialog.Footer>
<Button emphasis='strong' onClick={onClose}>
Start over
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>

Composition

Dialog is a compound component. Use Dialog.Header, Dialog.Body, and Dialog.Footer to structure the modal, and Dialog.Close to dismiss it.

<Dialog>
  <Dialog.Trigger render={<Button>View details</Button>} />
  <Dialog.Content>
    <Dialog.Header>
      <Dialog.Close render={<IconButton aria-label='Close' emphasis='subtle' size='sm' className='absolute left-0 top-0'><X weight='bold' /></IconButton>} />
      <IconTile size='2xl' shape='circle'>
        <Ticket weight='duotone' />
      </IconTile>
      <Dialog.Title>Order #1024</Dialog.Title>
      <Dialog.Description>Placed on 14 June 2026</Dialog.Description>
    </Dialog.Header>
    <Dialog.Body>
      <p className='text-sm text-subtle text-center'>Two tickets to the Saturday show, collected at the door.</p>
    </Dialog.Body>
  </Dialog.Content>
</Dialog>

A Dialog.Footer holds a centered actions row. Combine it with intent='danger' for a confirm-deletion flow.

<Dialog role='alertdialog'>
  <Dialog.Trigger render={<Button intent='danger' emphasis='strong'>Delete account</Button>} />
  <Dialog.Content intent='danger' size='sm'>
    <Dialog.Header>
      <IconTile size='2xl' shape='circle'>
        <Warning weight='duotone' />
      </IconTile>
      <Dialog.Title>Delete account?</Dialog.Title>
      <Dialog.Description>
        This permanently removes your account and cannot be undone.
      </Dialog.Description>
    </Dialog.Header>
    <Dialog.Footer>
      <Dialog.Close render={<Button>Cancel</Button>} />
      <Dialog.Close render={<Button intent='danger' emphasis='strong'>Delete</Button>} />
    </Dialog.Footer>
  </Dialog.Content>
</Dialog>

Guidelines

Use a dialog for tasks that need the user's full attention — confirmations, short forms, and destructive actions. Keep content concise, and set intent on Dialog.Content, not the trigger, since portaled popups do not inherit the trigger's CSS cascade.

Use alertdialog for blocking or destructive confirmations

<Dialog role='alertdialog'>
<Dialog.Content intent='danger' size='sm'>
<Dialog.Title>Delete event?</Dialog.Title>
</Dialog.Content>
</Dialog>

Do

Set role='alertdialog' when the user must resolve the dialog before moving on — deleting data, discarding edits, a session or cart expiry. Screen readers announce it assertively, and it stacks above an already-open dialog or drawer on the z-alert tier.

<Dialog>
<Dialog.Content intent='danger' size='sm'>
<Dialog.Title>Delete event?</Dialog.Title>
</Dialog.Content>
</Dialog>

Don’t

Don't leave a destructive or blocking confirmation as the default role='dialog' — it is announced as a plain dialog and can be obscured by an open drawer. Keep role='dialog' for informational or optional content.

Name buttons by their action

Do

Label the confirming button with the specific verb and object the user is committing to. A clear action like "Save changes" lets people act without rereading the title, and for destructive flows naming the consequence — "Delete event" — leaves no doubt about what happens.

Don’t

Vague labels like "OK", "Yes", or "Submit" tell the user nothing on their own. They have to jump back to the title to work out what they are actually agreeing to.

Write a title that states the decision

Delete event?

Do

A title that names the affected object and the action — a clear question or noun phrase — tells the user exactly what is at stake before they read another word.

Are you sure?

Don’t

Vague titles that never name the affected object force the user to hunt through the body for context, and make them hesitate before acting.

Use one prominent action, the rest normal

Do

Give the dialog one prominent button — emphasis='strong' with an accent intent, or danger for a destructive task — and leave the rest as default buttons. The primary path stays obvious at a glance.

Don’t

Never use emphasis='subtle' for footer actions, and never give a dialog more than one accent or strong button. Competing prominent buttons make the user stop and weigh every option instead of following the intended path.

Dismiss with a header close, not a lone Cancel

Do

When the only action is to close, put a Close icon button in the top-left of the header and skip the footer entirely. The dialog reads as informational, not a decision awaiting a choice.

Don’t

A lone Cancel in the footer wastes the prominent action slot and adds a step. There is nothing to cancel — the user only wants to close.

Lead notable dialogs with a matching icon

Do

For confirmations and notable moments, centre a size='2xl' shape='circle' IconTile above the title and leave its intent unset so it inherits the dialog's — a danger or warning tile for caution, success for completion. It signals the nature of the action before the title is read.

Don’t

Don't give the tile an intent that clashes with the dialog, shrink it until it reads as decoration, or add one to routine dialogs where it is just noise.

Accessibility

  • Keyboard: Escape closes the dialog and returns focus to the trigger. Focus is trapped within the dialog while it is open.
  • Default action: Enter activates the strong primary action (the emphasis='strong' button), like a form's default submit. It is ignored while focus is on another button, a multi-line textarea, or editable content so those keep their own Enter behaviour.
  • Focus: Focus moves into the dialog on open and is restored to the trigger on close.
  • ARIA: The popup is exposed with role="dialog" and aria-modal, with Dialog.Title/Dialog.Description providing the accessible name and description. Set role='alertdialog' for a blocking message that needs a response — this also raises the dialog to the z-alert tier so it stacks above an open dialog or drawer.
  • Icon-only close: When the dismiss control shows only an icon, give it an explicit label — e.g. <Dialog.Close render={<IconButton aria-label='Close'><XIcon weight='bold' /></IconButton>} /> — so screen readers announce its purpose.

API reference

Dialog

openboolean

Whether the dialog is currently open.

defaultOpenboolean

Whether the dialog is initially open. To render a controlled dialog, use the `open` prop instead.

Defaults to false.

modalboolean | "trap-focus"

Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled. When `modal` is `true` or `'trap-focus'`, render `<Dialog.Close>` inside `<Dialog.Popup>` so touch screen readers can escape the popup.

Defaults to true.

onOpenChange((open: boolean, eventDetails: DialogRootChangeEventDetails) => void)

Event handler called when the dialog is opened or closed.

onOpenChangeComplete((open: boolean) => void)

Event handler called after any animations complete when the dialog is opened or closed.

disablePointerDismissalboolean

Determines whether the dialog should close on outside clicks.

Defaults to false.

actionsRefRefObject<DialogRootActions | null>

A ref to imperative actions. - `unmount`: When specified, the dialog will not be unmounted when closed. Instead, the `unmount` function must be called to unmount the dialog manually. Useful when the dialog's animation is controlled by an external library. - `close`: Closes the dialog imperatively when called.

handleDialogHandle<unknown>

A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method.

childrenReactNode | PayloadChildRenderFunction<unknown>

The content of the dialog. This can be a regular React node or a render function that receives the `payload` of the active trigger.

triggerIdstring | null

ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).

defaultTriggerIdstring | null

ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog.

role"dialog" | "alertdialog"

`'alertdialog'` raises the dialog to the `z-alert` tier so it stacks above an open modal or drawer.

Defaults to 'dialog'.

Dialog.Backdrop

Inherited from DialogBackdropProps

forceRenderboolean

Whether the backdrop is forced to render even when nested.

Defaults to false.

Dialog.Body

No additional props — forwards all standard HTML attributes to the underlying element.

Dialog.Close

Inherited from NativeButtonProps

nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (e.g. `<div>`).

Defaults to true.

Dialog.Content

intent"neutral" | "brand" | "brand-secondary" | "accent" | "danger" | "success" | "warning" | "info" | null
size"sm" | "md" | "lg" | null

Inherited from DialogPopupProps

initialFocusboolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the dialog is opened. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.

finalFocusboolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the dialog is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.

Dialog.Description

No additional props — forwards all standard HTML attributes to the underlying element.

Dialog.Footer

No additional props — forwards all standard HTML attributes to the underlying element.

Dialog.Header

No additional props — forwards all standard HTML attributes to the underlying element.

Dialog.Popup

intent"neutral" | "brand" | "brand-secondary" | "accent" | "danger" | "success" | "warning" | "info" | null
size"sm" | "md" | "lg" | null

Inherited from DialogPopupProps

initialFocusboolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the dialog is opened. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.

finalFocusboolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)

Determines the element to focus when the dialog is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.

Dialog.Portal

Inherited from DialogPortalProps

keepMountedboolean

Whether to keep the portal mounted in the DOM while the popup is hidden.

Defaults to false.

containerHTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null

A parent element to render the portal element into.

Dialog.Title

No additional props — forwards all standard HTML attributes to the underlying element.

Dialog.Trigger

Inherited from DialogTriggerProps

handleDialogHandle<unknown>

A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method.

payloadunknown

A payload to pass to the dialog when it is opened.

idstring

ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialogs in controlled mode (with the DialogRoot `triggerId` prop).

Inherited from NativeButtonProps

nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (e.g. `<div>`).

Defaults to true.

Dialog.Viewport

No additional props — forwards all standard HTML attributes to the underlying element.