Dialog

A modal dialog that interrupts the user with important content and expects a response.

Usage

Dialog is fully composable: you assemble the portal, overlay, and panel yourself, so your app controls layering and every user-facing string. For a dialog that becomes a drawer on mobile, see Responsive Dialog.

import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogOverlay,
  DialogPortal,
  DialogTitle,
  DialogTrigger,
} from '@epilot/spark-ui/dialog'
<Dialog>
  <DialogTrigger
    render={
      <Button styleVariant="surface" color="neutral">
        Open Dialog
      </Button>
    }
  />
  <DialogPortal>
    <DialogOverlay />
    <DialogContent>
      <DialogClose aria-label={t('close')} />
      <DialogHeader>
        <DialogTitle>Are you sure?</DialogTitle>
        <DialogDescription>
          This action cannot be undone. This will permanently delete your
          account and remove your data from our servers.
        </DialogDescription>
      </DialogHeader>
      <DialogFooter>
        <DialogClose
          render={
            <Button styleVariant="surface" color="neutral">
              Cancel
            </Button>
          }
        />
        <Button>Continue</Button>
      </DialogFooter>
    </DialogContent>
  </DialogPortal>
</Dialog>

Examples

Controlled Dialog

Use the open and onOpenChange props to control the dialog state programmatically.

const [open, setOpen] = React.useState(false)

return (
  <Dialog open={open} onOpenChange={setOpen}>
    <DialogTrigger render={<Button>Open</Button>} />
    <DialogPortal>
      <DialogOverlay />
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Controlled Dialog</DialogTitle>
          <DialogDescription>
            This dialog's open state is controlled by React state.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button onClick={() => setOpen(false)}>Close</Button>
        </DialogFooter>
      </DialogContent>
    </DialogPortal>
  </Dialog>
)

Cap the panel height and give the grid explicit rows: auto header, minmax(0,1fr) body, auto footer. The body scrolls; header and footer stay visible.

Close Button

A bare DialogClose placed directly inside DialogContent renders the ready-made corner X. Pass a translated aria-label from your app; the library ships no default text. Give it children for a visible text label, or a render prop to merge into your own element, e.g. a footer Button that closes the dialog (children would nest a <button> inside the close <button>).

<DialogContent>
  <DialogClose aria-label={t('close')} />
  {/* ... */}
  <DialogFooter>
    <DialogClose
      render={
        <Button styleVariant="surface" color="neutral">
          Cancel
        </Button>
      }
    />
    <Button>Confirm</Button>
  </DialogFooter>
</DialogContent>

API Reference

Built on top of Base UI Dialog. See the Base UI documentation for the full API.

On this page