Skip to main content

Overview

Dialog is a modal component that displays content in a centered overlay with a backdrop. It supports nested dialogs with automatic scaling animations, focus trapping, and keyboard controls.

Basic usage

import { Dialog } from '@raystack/apsara';

<Dialog>
  <Dialog.Trigger asChild>
    <button>Open dialog</button>
  </Dialog.Trigger>
  
  <Dialog.Content>
    <Dialog.Header>
      <Dialog.Title>Dialog title</Dialog.Title>
    </Dialog.Header>
    
    <Dialog.Body>
      <Dialog.Description>
        This is the dialog content.
      </Dialog.Description>
    </Dialog.Body>
    
    <Dialog.Footer>
      <Dialog.Close asChild>
        <button>Cancel</button>
      </Dialog.Close>
      <button>Confirm</button>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog>

Components

Dialog (Root)

The root component that manages dialog state and context. Based on @base-ui/react Dialog.Root.
open
boolean
Controls the open state of the dialog (controlled mode).
defaultOpen
boolean
The initial open state in uncontrolled mode.
onOpenChange
(open: boolean) => void
Callback fired when the open state changes.
modal
boolean
default:"true"
Whether the dialog is modal (blocks interaction with content behind it).

Dialog.Trigger

A button that opens the dialog when clicked.
asChild
boolean
When true, merges props with the immediate child element instead of rendering a button.

Dialog.Content

The content container rendered in a portal with backdrop and viewport.
showCloseButton
boolean
default:"true"
Whether to show the close button in the top-right corner.
overlay
object
Props for the backdrop overlay, including a blur property to enable backdrop blur.
width
string | number
Custom width for the dialog content.
showNestedAnimation
boolean
default:"true"
Toggles nested dialog animation (scaling and translation) when dialogs are stacked.
className
string
Additional CSS classes for the dialog popup.

Dialog.Header

A flex container for the dialog header, typically containing the title and description.

Dialog.Title

The accessible title element for the dialog. Required for accessibility.

Dialog.Description

An optional description element that provides additional context about the dialog.

Dialog.Body

A flex column container for the main dialog content. A flex container aligned to the right for action buttons.

Dialog.Close

A button that closes the dialog when clicked.
asChild
boolean
When true, merges props with the immediate child element instead of rendering a button.

Dialog.CloseButton

A styled close button with an X icon, automatically positioned in the top-right corner.

Usage examples

Custom width dialog

<Dialog>
  <Dialog.Trigger asChild>
    <button>Open wide dialog</button>
  </Dialog.Trigger>
  
  <Dialog.Content width={800}>
    <Dialog.Header>
      <Dialog.Title>Wide dialog</Dialog.Title>
    </Dialog.Header>
    <Dialog.Body>
      Content with custom width.
    </Dialog.Body>
  </Dialog.Content>
</Dialog>

Dialog with blurred backdrop

<Dialog>
  <Dialog.Trigger asChild>
    <button>Open dialog</button>
  </Dialog.Trigger>
  
  <Dialog.Content overlay={{ blur: true }}>
    <Dialog.Header>
      <Dialog.Title>Blurred background</Dialog.Title>
    </Dialog.Header>
    <Dialog.Body>
      The backdrop behind this dialog is blurred.
    </Dialog.Body>
  </Dialog.Content>
</Dialog>

Nested dialogs

<Dialog>
  <Dialog.Trigger asChild>
    <button>Open first dialog</button>
  </Dialog.Trigger>
  
  <Dialog.Content>
    <Dialog.Header>
      <Dialog.Title>First dialog</Dialog.Title>
    </Dialog.Header>
    <Dialog.Body>
      <Dialog>
        <Dialog.Trigger asChild>
          <button>Open second dialog</button>
        </Dialog.Trigger>
        <Dialog.Content>
          <Dialog.Header>
            <Dialog.Title>Second dialog</Dialog.Title>
          </Dialog.Header>
          <Dialog.Body>
            Nested dialog with automatic scaling animation.
          </Dialog.Body>
        </Dialog.Content>
      </Dialog>
    </Dialog.Body>
  </Dialog.Content>
</Dialog>

Controlled dialog

function ControlledDialog() {
  const [open, setOpen] = useState(false);
  
  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <Dialog.Trigger asChild>
        <button>Open dialog</button>
      </Dialog.Trigger>
      
      <Dialog.Content>
        <Dialog.Header>
          <Dialog.Title>Controlled dialog</Dialog.Title>
        </Dialog.Header>
        <Dialog.Body>
          This dialog's state is controlled externally.
        </Dialog.Body>
        <Dialog.Footer>
          <button onClick={() => setOpen(false)}>Close</button>
        </Dialog.Footer>
      </Dialog.Content>
    </Dialog>
  );
}

Without close button

<Dialog>
  <Dialog.Trigger asChild>
    <button>Open dialog</button>
  </Dialog.Trigger>
  
  <Dialog.Content showCloseButton={false}>
    <Dialog.Header>
      <Dialog.Title>No close button</Dialog.Title>
    </Dialog.Header>
    <Dialog.Body>
      Use the footer buttons to close.
    </Dialog.Body>
    <Dialog.Footer>
      <Dialog.Close asChild>
        <button>Close</button>
      </Dialog.Close>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog>

Accessibility features

  • Focus trap: Focus is trapped within the dialog when open
  • ESC to close: Press Escape to close the dialog
  • Focus return: Focus returns to the trigger element when closed
  • ARIA labels: Proper ARIA attributes for screen readers
  • Close button: Includes accessible aria-label="Close dialog"
  • Modal behavior: Blocks interaction with content behind the dialog

Trigger patterns

The Dialog.Trigger component can be used in two ways:
  1. Default button: Renders a button element when no children are provided or asChild is false
  2. Custom trigger: Use asChild prop to merge dialog trigger functionality with your own button component
{/* Default button */}
<Dialog.Trigger>Open</Dialog.Trigger>

{/* Custom trigger */}
<Dialog.Trigger asChild>
  <button className="custom-button">Open</button>
</Dialog.Trigger>