AI Dimensie
Wave 11·Docs · part of AI Dimensie·Why Docs? →·⌘K jump

Accessible modal dialogs with focus management.

Dialog

The Dialog component is a modal overlay built on Radix UI's dialog primitive. It handles focus trapping, escape-to-close, click-outside-to-close, and scroll locking — all the tricky stuff done right.

Import#

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@aidimension/ui/components/dialog";

Basic usage#

<Dialog>
  <DialogTrigger asChild>
    <Button>Open dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you sure?</DialogTitle>
      <DialogDescription>
        This action cannot be undone.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button variant="destructive">Delete</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Controlled#

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

<Dialog open={open} onOpenChange={setOpen}>
  <DialogContent>
    {/* ... */}
    <Button onClick={() => setOpen(false)}>Close</Button>
  </DialogContent>
</Dialog>

With form#

<Dialog>
  <DialogTrigger asChild>
    <Button>Add user</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Add a new user</DialogTitle>
    </DialogHeader>
    <form onSubmit={handleSubmit}>
      <div className="space-y-3">
        <div>
          <Label htmlFor="name">Name</Label>
          <Input id="name" />
        </div>
        <div>
          <Label htmlFor="email">Email</Label>
          <Input id="email" type="email" />
        </div>
      </div>
      <DialogFooter className="mt-6">
        <Button type="submit">Invite</Button>
      </DialogFooter>
    </form>
  </DialogContent>
</Dialog>

Sizes#

Use the className prop to control width:

<DialogContent className="sm:max-w-sm">Small</DialogContent>
<DialogContent className="sm:max-w-md">Default</DialogContent>
<DialogContent className="sm:max-w-lg">Large</DialogContent>
<DialogContent className="sm:max-w-2xl">XL</DialogContent>
<DialogContent className="sm:max-w-4xl max-h-[90vh] overflow-y-auto">Full</DialogContent>

API reference#

ComponentDescription
DialogThe root. Manages open state and provides context.
DialogTriggerThe element that opens the dialog.
DialogContentThe modal panel. Renders into a portal.
DialogHeaderTop section, contains title + description.
DialogTitleRequired for screen readers.
DialogDescriptionOptional subtitle.
DialogFooterBottom section, usually with action buttons.
DialogCloseCloses the dialog when wrapped.

Accessibility#

Built on Radix, so you get:

  • ✅ Focus trapped inside the dialog
  • ✅ Tab cycles through focusable elements
  • ✅ Escape closes the dialog
  • ✅ Click outside closes (configurable)
  • ✅ aria-modal, aria-labelledby, aria-describedby set automatically
  • ✅ Body scroll locked while open
  • ✅ Return focus to the trigger on close

Common patterns#

Confirmation dialog#

<Dialog>
  <DialogTrigger asChild>
    <Button variant="destructive">Delete account</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Delete your account?</DialogTitle>
      <DialogDescription>
        This will permanently delete your account and all data. This cannot be undone.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button variant="destructive" onClick={handleDelete}>Yes, delete</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Scrollable long content#

<DialogContent className="max-h-[90vh] overflow-y-auto">
  {/* long content here */}
</DialogContent>

Non-modal dialog#

Set modal={false} on the root. The dialog won't trap focus or lock scroll, and you can interact with content behind it.