Skip to content

Dialog

The Dialog component provides a modal dialog with a compound component API. It is backed by Base UI's Dialog primitive.

Import

tsx
import { Dialog } from "@tailor-platform/app-shell";

Basic Usage

tsx
<Dialog.Root>
  <Dialog.Trigger render={<Button />}>Open</Dialog.Trigger>
  <Dialog.Content>
    <Dialog.Header>
      <Dialog.Title>Confirm Action</Dialog.Title>
      <Dialog.Description>Are you sure you want to proceed?</Dialog.Description>
    </Dialog.Header>
    <Dialog.Footer>
      <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
      <Button>Confirm</Button>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog.Root>

Sub-components

Sub-componentDescription
Dialog.RootManages open/close state
Dialog.TriggerElement that opens the dialog when clicked
Dialog.ContentThe main dialog panel (includes overlay and close button automatically)
Dialog.HeaderLayout wrapper for title and description
Dialog.FooterLayout wrapper for action buttons
Dialog.TitleDialog title (announced by screen readers)
Dialog.DescriptionAdditional context below the title
Dialog.CloseButton that closes the dialog

Props

Dialog.Root Props

PropTypeDefaultDescription
openboolean-Controlled open state
defaultOpenbooleanfalseInitial open state (uncontrolled)
onOpenChange(open: boolean) => void-Callback when open state changes
modalbooleantrueWhether the dialog is modal
childrenReact.ReactNode-Dialog sub-components

Dialog.Content Props

Accepts className and all standard HTML <div> props.

Dialog.Trigger / Dialog.Close Props

Accept a render prop for custom element rendering (Base UI render pattern), plus standard button props.

Controlled Usage

tsx
const [open, setOpen] = useState(false);

<Dialog.Root open={open} onOpenChange={setOpen}>
  <Dialog.Trigger render={<Button variant="outline" />}>Open Dialog</Dialog.Trigger>
  <Dialog.Content>
    <Dialog.Header>
      <Dialog.Title>Settings</Dialog.Title>
    </Dialog.Header>
    <Dialog.Footer>
      <Button onClick={() => setOpen(false)}>Done</Button>
    </Dialog.Footer>
  </Dialog.Content>
</Dialog.Root>;

Examples

Delete Confirmation

tsx
function DeleteConfirmation({ onDelete }: { onDelete: () => void }) {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={<Button variant="destructive" />}>Delete</Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Header>
          <Dialog.Title>Delete Order</Dialog.Title>
          <Dialog.Description>
            This action cannot be undone. The order will be permanently deleted.
          </Dialog.Description>
        </Dialog.Header>
        <Dialog.Footer>
          <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
          <Button variant="destructive" onClick={onDelete}>
            Delete
          </Button>
        </Dialog.Footer>
      </Dialog.Content>
    </Dialog.Root>
  );
}

Form Dialog

tsx
function EditDialog({ item }: { item: Item }) {
  const [name, setName] = useState(item.name);

  return (
    <Dialog.Root>
      <Dialog.Trigger render={<Button variant="outline" />}>Edit</Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Header>
          <Dialog.Title>Edit Item</Dialog.Title>
        </Dialog.Header>
        <Input value={name} onChange={(e) => setName(e.target.value)} />
        <Dialog.Footer>
          <Dialog.Close render={<Button variant="outline" />}>Cancel</Dialog.Close>
          <Button onClick={() => handleSave(name)}>Save</Button>
        </Dialog.Footer>
      </Dialog.Content>
    </Dialog.Root>
  );
}

Accessibility

  • Dialog title is announced by screen readers via Dialog.Title
  • Focus is trapped inside the dialog while open
  • Pressing Escape closes the dialog
  • The close button is always present inside Dialog.Content
  • Sheet - Slide-in panel alternative for non-modal workflows
  • Button - Use as trigger and action buttons