Light

Alert Dialog

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

Spec · from metadata

When to use

  • Confirming destructive actions (delete, remove, discard changes)
  • Blocking decisions that require explicit user acknowledgment before proceeding
  • Irreversible operations where the user must understand consequences
  • Logout or session-ending confirmations

When not to use

  • Non-destructive confirmations -- use Dialog instead
  • Informational messages that don't require action -- use Alert (inline) or Toast (transient)
  • Form collection or complex interactions -- use Dialog or Sheet
  • Mobile-first bottom panels -- use Drawer instead

Variants

PropValuesDefaultDescription
sizedefaultsmdefaultControls dialog width. 'sm' gives a narrower max-w-xs layout with grid-based footer; 'default' gives max-w-lg on sm+ screens.

Anti-patterns

Avoid<Dialog>
  <DialogContent>
    <DialogTitle>Delete this item?</DialogTitle>
    <Button variant="destructive">Delete</Button>
  </DialogContent>
</Dialog>
Prefer<AlertDialog>
  <AlertDialogContent>
    <AlertDialogTitle>Delete this item?</AlertDialogTitle>
    <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
    <AlertDialogCancel>Cancel</AlertDialogCancel>
  </AlertDialogContent>
</AlertDialog>

Destructive confirmations must use AlertDialog, not Dialog. AlertDialog traps focus and prevents dismissal by clicking the overlay, ensuring the user makes an explicit choice.

Avoid<AlertDialogContent>
  <AlertDialogHeader>
    <AlertDialogTitle>Confirm</AlertDialogTitle>
  </AlertDialogHeader>
</AlertDialogContent>
Prefer<AlertDialogContent>
  <AlertDialogHeader>
    <AlertDialogTitle>Delete this project?</AlertDialogTitle>
    <AlertDialogDescription>
      This will permanently delete "My Project" and all associated data.
    </AlertDialogDescription>
  </AlertDialogHeader>
</AlertDialogContent>

Always include AlertDialogDescription for accessibility and clarity. Screen readers announce both title and description.

Accessibility

  • Required ARIAaria-labelledby (via AlertDialogTitle)aria-describedby (via AlertDialogDescription)
  • Screen readerAnnounced as an alert dialog. The title and description are automatically associated via Radix primitives. Overlay click does not dismiss -- users must use Cancel or Action.
  • ContrastOverlay uses bg-foreground/50 for sufficient contrast against page content.

Token bindings

TokenCategoryUsage
bg-cardcolorDialog content background
bg-foreground/50colorOverlay background
text-muted-foregroundcolorDescription text
bg-mutedcolorMedia slot background
shadow-lgshadowDialog elevation
rounded-lgradiusDialog border radius
duration-200motionOpen/close animation duration

Import

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@timelycare/helix-ui"

Props

interface AlertDialogProps {
  open?: boolean
  onOpenChange?: (open: boolean) => void
  children: React.ReactNode
}

interface AlertDialogTriggerProps {
  asChild?: boolean
  children: React.ReactNode
}

interface AlertDialogContentProps {
  className?: string
  size?: "default" | "sm"
  children: React.ReactNode
}

interface AlertDialogHeaderProps {
  className?: string
  children: React.ReactNode
}

interface AlertDialogFooterProps {
  className?: string
  children: React.ReactNode
}

interface AlertDialogTitleProps {
  className?: string
  children: React.ReactNode
}

interface AlertDialogDescriptionProps {
  className?: string
  children: React.ReactNode
}

interface AlertDialogActionProps {
  variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
  size?: "default" | "xs" | "sm" | "lg" | "icon"
  className?: string
  children: React.ReactNode
}

interface AlertDialogCancelProps {
  variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
  size?: "default" | "xs" | "sm" | "lg" | "icon"
  className?: string
  children: React.ReactNode
}

interface AlertDialogMediaProps {
  className?: string
  children: React.ReactNode
}

Variants

BreakpointViewportHeader AlignmentFooter LayoutButton WidthButton Order
md≥768pxLeft-alignedHorizontal, right-alignedAutoCancel, Action
sm<768pxCenter-alignedStacked, full-width100%Action, Cancel

Choosing a Variant

  • Use responsive footer classes to automatically switch: flex-col sm:flex-row sm:justify-end
  • Desktop: Cancel first, then Action (reading order, right-aligned)
  • Mobile: Action on top (stacked), Cancel below, both full-width

Sizes

SizeUse ForTailwind
defaultStandard dialogssm:max-w-lg (512px)
smCompact confirmationsmax-w-xs (320px)

Use the size prop on AlertDialogContent:

<AlertDialogContent size="sm">...</AlertDialogContent>

The sm size centers header text and uses a 2-column grid layout in the footer.


Styling

Typography

ElementFontSizeWeightLine HeightColor
Titlefont-sans (Adelle Sans)text-lg (18px)font-semibold (Bold)leading-7 (28px)text-foreground
Descriptionfont-sans (Adelle Sans)text-sm (14px)font-normal (Regular)leading-5 (20px)text-muted-foreground

Spacing

ElementPropertyValue
HeaderTitle-to-description gapgap-2 (8px)
FooterButton gapgap-2 (8px)
ContentHeader-to-footer gapComponent spacing (typically gap-4 or gap-6)

Buttons

ButtonVariantHeightPaddingText
Canceloutlineh-9 (36px)px-4 (16px)text-primary
Actiondefaulth-9 (36px)px-4 (16px)text-primary-foreground

Both buttons use:

  • Border radius: rounded-md (8px)
  • Shadow: shadow-xs (0px 1px 2px rgba(0,0,0,0.05))
  • Font: Adelle Sans Semibold, 14px

States

StateImplementation
ClosedNot visible (default)
OpenCentered dialog with dark overlay backdrop

Button states: Cancel and Action buttons follow standard Button component states.


Common Patterns

Basic Confirmation

<AlertDialog>
  <AlertDialogTrigger asChild>
    <Button variant="destructive">Delete Account</Button>
  </AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
      <AlertDialogDescription>
        This action cannot be undone. This will permanently delete your account.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction>Continue</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

Controlled Dialog

const [open, setOpen] = useState(false)

<AlertDialog open={open} onOpenChange={setOpen}>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Discard changes?</AlertDialogTitle>
      <AlertDialogDescription>
        You have unsaved changes. Are you sure you want to leave?
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Keep Editing</AlertDialogCancel>
      <AlertDialogAction onClick={handleDiscard}>Discard</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

Destructive Action Button

<AlertDialogAction variant="destructive">
  Delete
</AlertDialogAction>

With Media Icon

<AlertDialogContent>
  <AlertDialogHeader>
    <AlertDialogMedia>
      <TriangleAlert className="size-8 text-destructive" />
    </AlertDialogMedia>
    <AlertDialogTitle>Delete this item?</AlertDialogTitle>
    <AlertDialogDescription>
      This will permanently remove the item.
    </AlertDialogDescription>
  </AlertDialogHeader>
  <AlertDialogFooter>
    <AlertDialogCancel>Cancel</AlertDialogCancel>
    <AlertDialogAction variant="destructive">Delete</AlertDialogAction>
  </AlertDialogFooter>
</AlertDialogContent>

Compact Size

<AlertDialogContent size="sm">
  <AlertDialogHeader>
    <AlertDialogTitle>Discard draft?</AlertDialogTitle>
    <AlertDialogDescription>
      Your unsaved changes will be lost.
    </AlertDialogDescription>
  </AlertDialogHeader>
  <AlertDialogFooter>
    <AlertDialogCancel>Cancel</AlertDialogCancel>
    <AlertDialogAction>Discard</AlertDialogAction>
  </AlertDialogFooter>
</AlertDialogContent>

Component Structure

{/* Desktop (md+) */}
<div className="max-w-lg">
  
  {/* AlertDialogHeader */}
  <div className="flex flex-col gap-2 text-left">
    <h2 className="text-lg font-semibold leading-7 text-foreground">
      {title}
    </h2>
    <p className="text-sm font-normal leading-5 text-muted-foreground">
      {description}
    </p>
  </div>
  
  {/* AlertDialogFooter */}
  <div className="flex gap-2 items-center justify-end">
    <Button variant="outline" className="h-9 px-4">Cancel</Button>
    <Button className="h-9 px-4">Continue</Button>
  </div>
  
</div>

{/* Mobile (sm) */}
<div className="max-w-lg">
  
  {/* AlertDialogHeader */}
  <div className="flex flex-col gap-2 text-center">
    <h2 className="text-lg font-semibold leading-7 text-foreground">
      {title}
    </h2>
    <p className="text-sm font-normal leading-5 text-muted-foreground">
      {description}
    </p>
  </div>
  
  {/* AlertDialogFooter - stacked, action first */}
  <div className="flex flex-col gap-2 w-full">
    <Button className="h-9 px-4 w-full">Continue</Button>
    <Button variant="outline" className="h-9 px-4 w-full">Cancel</Button>
  </div>
  
</div>

Accessibility

  • Focus trapped within dialog when open
  • Escape key triggers cancel action
  • Initial focus goes to Cancel button (safest action)
  • Screen readers announce as "alertdialog" role
  • Title/description linked via aria-labelledby/aria-describedby

Gotchas

ProblemSolution
Click outside doesn't closeBy design - requires explicit button action
Buttons stack wrong on mobileUse flex-col sm:flex-row sm:justify-end on footer
Need async action before closeUse controlled open state with onOpenChange
Action should look dangerousUse variant="destructive" on AlertDialogAction
Mobile text not centeredAdd text-center to header on mobile breakpoint
Button order reversed on mobileAction button should be first (on top) on mobile

See Also


Figma Reference

View in Figma


Last updated: February 23, 2026