Alert Dialog

Usage

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger
} from '@jamie/ui/alert-dialog'
import { Button } from '@jamie/ui/button'
 
export function AlertDialogDemo() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>
        Share Transcript
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Share meeting transcript?</AlertDialogTitle>
          <AlertDialogDescription>
            This will share the full transcript of "Q3 Revenue Review" with all meeting
            participants. They will receive an email notification.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Share</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Destructive

Use a destructive action button for irreversible operations. Combine with AlertDialogMedia to add a visual icon.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger
} from '@jamie/ui/alert-dialog'
import { Button } from '@jamie/ui/button'
import { Trash2Icon } from 'lucide-react'
 
export function AlertDialogDestructiveDemo() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="destructive" />}>
        Delete Meeting
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogMedia>
            <Trash2Icon />
          </AlertDialogMedia>
          <AlertDialogTitle>Delete this meeting?</AlertDialogTitle>
          <AlertDialogDescription>
            This will permanently delete the recording, transcript, and all AI-generated notes for
            "Weekly Standup — Jan 15". This action cannot be undone.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Small Size

Use size="sm" on AlertDialogContent for a more compact dialog layout.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger
} from '@jamie/ui/alert-dialog'
import { Button } from '@jamie/ui/button'
 
export function AlertDialogSmallDemo() {
  return (
    <AlertDialog>
      <AlertDialogTrigger render={<Button variant="outline" />}>Stop Recording</AlertDialogTrigger>
      <AlertDialogContent size="sm">
        <AlertDialogHeader>
          <AlertDialogTitle>Stop recording?</AlertDialogTitle>
          <AlertDialogDescription>
            Jamie will stop recording this meeting. You can restart it at any time.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Stop</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}

Imperative API

Open an alert dialog from anywhere — no inline trigger, no provider, no hook. Use defineAlertDialog for confirmations triggered from menus, hotkeys, or async flows.

'use client'
 
import {
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  defineAlertDialog
} from '@jamie/ui/alert-dialog'
import { Button } from '@jamie/ui/button'
import { toast } from '@jamie/ui/sonner'
 
const deleteMeetingDialog = defineAlertDialog<{ title: string; onConfirm: () => void }>(
  ({ title, onConfirm }) => (
    <AlertDialogContent>
      <AlertDialogHeader>
        <AlertDialogTitle>Delete &ldquo;{title}&rdquo;?</AlertDialogTitle>
        <AlertDialogDescription>
          This will permanently delete the recording, transcript, and AI-generated notes for this
          meeting. This action cannot be undone.
        </AlertDialogDescription>
      </AlertDialogHeader>
      <AlertDialogFooter>
        <AlertDialogCancel>Cancel</AlertDialogCancel>
        <AlertDialogAction
          variant="destructive"
          onClick={() => {
            onConfirm()
            deleteMeetingDialog.close()
          }}
        >
          Delete
        </AlertDialogAction>
      </AlertDialogFooter>
    </AlertDialogContent>
  )
)
 
export function AlertDialogImperativeDemo() {
  return (
    <>
      <Button
        variant="outline"
        onClick={() =>
          deleteMeetingDialog.open({
            title: 'Q3 Revenue Review',
            onConfirm: () => toast.success('Meeting deleted')
          })
        }
      >
        Delete Meeting
      </Button>
      <deleteMeetingDialog.Root />
    </>
  )
}

Define the dialog

defineAlertDialog<Payload>(render) returns a { open, close, Root } object


Keep it module-scope so any caller can import it. The render function receives the typed payload from .open(payload).

import {
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  defineAlertDialog
} from '@jamie/ui/alert-dialog'
 
export const deleteMeetingDialog = defineAlertDialog<{
  title: string
  onConfirm: () => void
}>(({ title, onConfirm }) => (
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Delete &ldquo;{title}&rdquo;?</AlertDialogTitle>
      <AlertDialogDescription>
        This will permanently delete the recording, transcript, and AI-generated notes for this
        meeting. This action cannot be undone.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction
        variant="destructive"
        onClick={() => {
          onConfirm()
          deleteMeetingDialog.close()
        }}
      >
        Delete
      </AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
))

Mount the Root once

Render <deleteMeetingDialog.Root /> once, somewhere persistent — typically a top-level AppDialogs component.

export function AppDialogs() {
  return <deleteMeetingDialog.Root />
}

The Root portals to <body> and lazy-mounts its content only when the dialog opens.

Open from anywhere

Import the dialog and call .open(payload). Payload is typed.

import { toast } from '@jamie/ui/sonner'
 
deleteMeetingDialog.open({
  title: 'Q3 Revenue Review',
  onConfirm: () => {
    deleteMeeting()
    toast.success('Meeting deleted')
  }
})