Light

Dialog

A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.

Spec · from metadata

When to use

  • Confirming a user action that needs focused attention
  • Displaying supplementary content without navigating away
  • Short forms or configuration panels that overlay the current view

When not to use

  • Destructive action confirmation — use AlertDialog instead (it blocks background interaction and requires explicit choice)
  • Simple tooltips or popovers — use Popover or Tooltip
  • Full-page forms — use a dedicated route or Card-based layout
  • Non-blocking notifications — use Toast or Alert

Anti-patterns

Avoid<Dialog>
  <DialogContent>
    <p>Are you sure you want to delete?</p>
    <Button variant="destructive">Delete</Button>
  </DialogContent>
</Dialog>
Prefer<AlertDialog>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you sure?</AlertDialogTitle>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction>Delete</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

Destructive confirmations MUST use AlertDialog which requires an explicit user choice and cannot be dismissed by clicking the overlay.

Avoid<DialogContent>
  <h2>Title</h2>
</DialogContent>
Prefer<DialogContent>
  <DialogHeader>
    <DialogTitle>Title</DialogTitle>
  </DialogHeader>
</DialogContent>

Radix requires DialogTitle for accessibility. Use DialogHeader + DialogTitle.

Accessibility

  • Required ARIAaria-labelledby (auto via DialogTitle)aria-describedby (auto via DialogDescription)
  • Screen readerClose button includes sr-only 'Close' text. Radix manages aria attributes automatically when DialogTitle and DialogDescription are present.

Token bindings

TokenCategoryUsage
bg-cardcolorDialog content background
bg-foreground/50colorOverlay backdrop
shadow-lgshadowDialog elevation
rounded-lgradiusContent border radius
duration-200motionOpen/close animation

Import

import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
  DialogClose,
} from "@timelycare/helix-ui"

Props

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

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

Design Tokens

Dialog Content

PropertyTokenValue
Backgroundbg-backgroundwhite
Borderborder1px solid border color
Border radiusrounded-lg
Shadowshadow-lgMulti-layer drop shadow
Paddingp-624px

Dialog Header

PropertyTokenValue
Gapgap-1.56px between title and description
Alignment (lg)text-leftLeft-aligned
Alignment (sm)text-centerCenter-aligned

Typography

ElementSizeColorWeight
Titletext-lg (18px)text-foregroundsemibold
Descriptiontext-sm (14px)text-muted-foregroundnormal

Dialog Footer

PropertyDesktop (lg)Mobile (sm)
Directionflex-rowflex-col
Gapgap-2 (8px)gap-2 (8px)
Alignmentjustify-enditems-start (full width)
Button widthAutoFull width (w-full)
Button orderCancel → PrimaryPrimary → Cancel

Close Icon

PropertyTokenValue
Sizesize-416px × 16px
Positionright-4 top-415px from edges
Border radiusrounded-xs2px
Default opacityopacity-7070%
ColorinheritedInherits from parent foreground

Buttons

PropertyTokenValue
Heighth-936px
Paddingpx-4 py-216px horizontal, 8px vertical
Border radiusrounded-md8px
Shadowshadow-xsSubtle drop shadow

Breakpoints

BreakpointUse ForLayout
lgDesktopSide-by-side footer buttons, left-aligned header
smMobileStacked footer buttons (full-width), centered header

Responsive Behavior

  • On mobile (sm), buttons stack vertically with primary action first, header text centers
  • On desktop (lg), buttons align horizontally in footer with cancel first, header left-aligned

Content Types

TypeUse For
FormCollecting user input (name, email, settings)
TextDisplaying information, confirmations, long content

States

StateImplementation
OpenControlled via open prop or DialogTrigger
ClosedonOpenChange(false) or close button click
Overlaybg-black/80 backdrop, closes on click

Close Icon States

StateImplementation
Defaultopacity-70
Hoveropacity-100
Focusring-2 ring-ring ring-offset-2

Common Patterns

Form Dialog

<Dialog>
  <DialogTrigger asChild>
    <Button>Edit Profile</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Dialog title</DialogTitle>
      <DialogDescription>This is a dialog description.</DialogDescription>
    </DialogHeader>
    <div className="space-y-4 py-4">
      <Input placeholder="Name" />
      <Input placeholder="Username" />
    </div>
    <DialogFooter>
      <DialogClose asChild>
        <Button variant="outline">Cancel</Button>
      </DialogClose>
      <Button>Save changes</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Text/Confirmation Dialog

<DialogContent>
  <DialogHeader>
    <DialogTitle>Dialog title</DialogTitle>
    <DialogDescription>This is a dialog description.</DialogDescription>
  </DialogHeader>
  <div className="text-sm text-muted-foreground">
    <p>Lorem ipsum dolor sit amet...</p>
  </div>
  <DialogFooter>
    <DialogClose asChild>
      <Button variant="outline">Cancel</Button>
    </DialogClose>
  </DialogFooter>
</DialogContent>

Success Confirmation Dialog

<Dialog open={showSuccess} onOpenChange={setShowSuccess}>
  <DialogContent className="sm:max-w-[400px]">
    <div className="flex flex-col items-center text-center py-4">
      <div className="size-16 rounded-full bg-accent flex items-center justify-center mb-4">
        <CircleCheck className="size-8 text-accent-foreground" />
      </div>
      <DialogTitle>Payment Successful</DialogTitle>
      <DialogDescription className="mt-2">
        Your payment of $99.00 has been processed.
      </DialogDescription>
    </div>
    <DialogFooter>
      <Button className="w-full" onClick={() => setShowSuccess(false)}>
        Continue
      </Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Controlled Dialog

const [open, setOpen] = useState(false)

<Dialog open={open} onOpenChange={setOpen}>
  <DialogContent>/* ... */</DialogContent>
</Dialog>

Accessibility

  • Built on Radix Dialog — handles focus trap and keyboard automatically
  • Focus trapped within dialog when open; returns to trigger on close
  • Keyboard: Escape to close, Tab/Shift+Tab to cycle focus
  • Screen reader: announces as dialog role with aria-modal="true"
  • Title/description linked via aria-labelledby/aria-describedby

Gotchas

ProblemSolution
Dialog won't closeUse DialogClose wrapper or control via onOpenChange
Trigger not workingAdd asChild prop to DialogTrigger
Footer buttons misalignedUse DialogFooter — handles responsive layout
Content overflowsAdd max-h-[80vh] overflow-y-auto to content area

See Also


Last updated: February 9, 2026