Components
Dialog
A modal window above a scrim, in five widths. Focus moves into it when it opens and goes back to the trigger when it closes. It uses shadow-high, the shadow kept for floating layers.
Preview
import { Button } from "@/components/ui/button"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>Rename project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Pick a name your team will recognize.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>Usage
When to use
- A short task that should block the page until it is done or cancelled, such as renaming a project.
- A confirmation that is not destructive.
- A task that needs room, such as a table or a preview, with a larger size.
When not to use
- Confirming a destructive action. Use AlertDialog.
- Long forms or reading. Give them their own page.
- Opening a dialog from a dialog. Close the first one before the next one opens, or give the flow its own page.
Variants
size
Set on DialogContent. Widest the panel gets: sm 448px (short confirmations), md 512px (forms of a few fields), lg 672px, xl 896px (tables, previews), full the whole viewport minus spacing-lg on each side, in height too. On a narrow screen every size fills the width minus spacing-lg.
import { Button } from "@/components/ui/button"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>Rename project</Button>
</DialogTrigger>
<DialogContent size="sm">
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Pick a name your team will recognize.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button>Rename project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Pick a name your team will recognize.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button>Rename project</Button>
</DialogTrigger>
<DialogContent size="lg">
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Pick a name your team will recognize.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button>Rename project</Button>
</DialogTrigger>
<DialogContent size="xl">
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Pick a name your team will recognize.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button>Rename project</Button>
</DialogTrigger>
<DialogContent size="full">
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Pick a name your team will recognize.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>Examples
Default
import { Button } from "@/components/ui/button"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
<Dialog>
<DialogTrigger asChild>
<Button>Rename project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>Pick a name your team will recognize.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>Do and don't
Do
- Use a dialog for a short task or a confirmation that should block the page until done or cancelled.
- Pick the smallest size that fits the content: sm for a sentence and two buttons, md for a short form, lg or xl for tables and previews. Long content scrolls inside the panel, which stays spacing-lg from the viewport edges.
- Let Escape, the close button and a scrim click close it.
- Open and close without animation (current behavior). If an animation is added later, reduced motion gets a fade only.
- Return focus to the trigger on close.
Don't
- Don't use Dialog to confirm a destructive action. Use AlertDialog, which focuses Cancel first and doesn't close on a scrim click.
- Don't open a dialog from a dialog.
- Don't use a dialog for long forms or reading; use a page or sheet. A bigger size does not make a long form short.
- Don't use size full for a short task; the user loses sight of the page they came from.
- Don't leave out DialogTitle. If it shouldn't show, make it sr-only.
- Don't remove shadow-high from the panel. A dialog floats above the page, so a border alone isn't enough.
Props
| Name | Type | Values | Default | Description |
|---|---|---|---|---|
| open | boolean | - | - | Set on Dialog. Whether the dialog is open, when you control it. Pair it with onOpenChange. Only on web. |
| defaultOpen | boolean | - | false | Set on Dialog. Opens the dialog on first render, when you don't control it. Only on web. |
| onOpenChange | function | - | - | Set on Dialog. Called with true or false when the dialog opens or closes (trigger, close button, Escape, scrim). Only on web. |
| modal | boolean | - | true | Set on Dialog. true traps focus and hides the rest of the page from assistive tech. Keep it true; false only for a panel the user works next to, which is better as a sheet. Only on web. |
| size | enum | sm | md | lg | xl | full | md | Set on DialogContent. Widest the panel gets: sm 448px (short confirmations), md 512px (forms of a few fields), lg 672px, xl 896px (tables, previews), full the whole viewport minus spacing-lg on each side, in height too. On a narrow screen every size fills the width minus spacing-lg. |
| showCloseButton | boolean | - | true | DialogContent: show the corner close button. DialogFooter takes the same prop (default false) to add a secondary Close button. Only on web. |
Slots
| Name | Required | Description |
|---|---|---|
| trigger | no | Element that opens the dialog. |
| title | yes | Dialog title (required for a11y). |
| description | no | Optional supporting text. |
| content | no | Main body. |
| footer | no | Actions. |
Anatomy
| Part | Description |
|---|---|
| root | Root. Controls open state. |
| overlay | Scrim behind the content. color-overlay-scrim. |
| content | The floating panel: raised surface, radius-page, shadow-high, border. |
| header | Top block for title and description. |
| title | Accessible name of the dialog. |
| description | Optional supporting text. |
| footer | Actions row at the bottom. |
| close | Icon button that dismisses the dialog. |
| icon | Tabler XIcon inside the close control, 16px. |
| hitArea | Transparent ::before on the close control, never smaller than size-target-min. |
Accessibility
Role: dialog
| Keys | Action |
|---|---|
| Escape | Closes the dialog and returns focus to the trigger. |
| Tab | Cycles focus inside the dialog (focus trap). |
- Traps focus and closes on Escape. Hides the rest of the page from assistive tech (aria-hidden on siblings); it does not set aria-modal.
- Always provide a DialogTitle (visible or sr-only) so the dialog has an accessible name.
- The corner close is a DialogClose with an sr-only "Close" label and a Tabler XIcon. If showCloseButton is false on DialogContent, the footer must have a Cancel or Close.
- Do not nest dialogs.
Install
npx shadcn add https://werewolf-ui.vercel.app/r/dialog.jsonThis also adds styles/werewolf.css, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after @import "tailwindcss", with a path relative to that CSS file (from app/globals.css it is @import "../styles/werewolf.css").