Skip to main content

Installation

npm install @base-ui/react
Or use the CLI:
npx shadcn@latest add dialog

Usage

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
<Dialog>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you absolutely sure?</DialogTitle>
      <DialogDescription>
        This action cannot be undone. This will permanently delete your account
        and remove your data from our servers.
      </DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>

Component API

Dialog

The root dialog component built on Radix UI. Props:
  • open - Control dialog open state
  • onOpenChange - Callback when open state changes
  • defaultOpen - Initial open state
  • modal - Whether dialog is modal (default: true)

DialogTrigger

The element that opens the dialog. Props:
  • asChild - Compose with child element

DialogContent

The dialog content container. Props:
  • showCloseButton - Show close button (default: true)
  • onEscapeKeyDown - Callback when escape is pressed
  • onPointerDownOutside - Callback when clicking outside

DialogHeader

Container for title and description.

DialogTitle

The dialog title (required for accessibility).

DialogDescription

The dialog description.

DialogFooter

Container for dialog actions. Props:
  • showCloseButton - Show close button (default: false)

DialogClose

Closes the dialog.

Examples

Custom Close Button

Replace the default close control with your own button:
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"

export function CustomCloseButton() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent showCloseButton={false}>
        <DialogHeader>
          <DialogTitle>Custom Close Button</DialogTitle>
          <DialogDescription>
            This dialog uses a custom close button in the footer.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose asChild>
            <Button variant="outline">Cancel</Button>
          </DialogClose>
          <Button>Save changes</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

No Close Button

Hide the close button completely:
<Dialog>
  <DialogTrigger asChild>
    <Button>Open</Button>
  </DialogTrigger>
  <DialogContent showCloseButton={false}>
    <DialogHeader>
      <DialogTitle>No Close Button</DialogTitle>
      <DialogDescription>
        This dialog has no close button.
      </DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>

Scrollable Content

Long content can scroll while the header stays in view:
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"

export function ScrollableDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open</Button>
      </DialogTrigger>
      <DialogContent className="max-h-[80vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>Terms and Conditions</DialogTitle>
          <DialogDescription>
            Please read and accept our terms and conditions.
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-4">
          {Array.from({ length: 20 }).map((_, i) => (
            <p key={i}>
              This is example content paragraph {i + 1}. The dialog content
              scrolls while the header and footer remain visible.
            </p>
          ))}
        </div>
      </DialogContent>
    </Dialog>
  )
}
Keep actions visible while the content scrolls:
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"

export function StickyFooterDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open</Button>
      </DialogTrigger>
      <DialogContent className="flex max-h-[80vh] flex-col">
        <DialogHeader>
          <DialogTitle>Long Form</DialogTitle>
          <DialogDescription>
            Fill out the form below to continue.
          </DialogDescription>
        </DialogHeader>
        <div className="flex-1 overflow-y-auto">
          {Array.from({ length: 20 }).map((_, i) => (
            <div key={i} className="mb-4">
              <label className="mb-2 block text-sm font-medium">
                Field {i + 1}
              </label>
              <input
                type="text"
                className="w-full rounded border px-3 py-2"
              />
            </div>
          ))}
        </div>
        <DialogFooter className="sticky bottom-0 bg-background pt-4">
          <Button variant="outline">Cancel</Button>
          <Button>Submit</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Controlled Dialog

Control the dialog open state:
import * as React from "react"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"

export function ControlledDialog() {
  const [open, setOpen] = React.useState(false)

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Controlled Dialog</DialogTitle>
          <DialogDescription>
            This dialog's open state is controlled.
          </DialogDescription>
        </DialogHeader>
        <Button onClick={() => setOpen(false)}>Close from inside</Button>
      </DialogContent>
    </Dialog>
  )
}

Form Dialog

Dialog containing a form:
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export function FormDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Edit Profile</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Edit profile</DialogTitle>
          <DialogDescription>
            Make changes to your profile here. Click save when you're done.
          </DialogDescription>
        </DialogHeader>
        <div className="grid gap-4 py-4">
          <div className="grid grid-cols-4 items-center gap-4">
            <Label htmlFor="name" className="text-right">
              Name
            </Label>
            <Input id="name" defaultValue="Pedro Duarte" className="col-span-3" />
          </div>
          <div className="grid grid-cols-4 items-center gap-4">
            <Label htmlFor="username" className="text-right">
              Username
            </Label>
            <Input id="username" defaultValue="@peduarte" className="col-span-3" />
          </div>
        </div>
        <DialogFooter>
          <Button type="submit">Save changes</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Accessibility

  • Built on Radix UI for full accessibility
  • Focus is trapped within the dialog
  • Escape key closes the dialog
  • Background content is inert
  • Screen reader compatible
  • ARIA labels and roles

API Reference

See the Base UI Dialog documentation for complete API details.

Build docs developers (and LLMs) love