Dialog
A focused surface for decisions that need attention.
modaloverlay
Preview
Dialog preview
Live · keyboard ready
When to use
- Confirmations and decisions that must interrupt, such as Delete project.
- Short forms like rename or invite that fit in one focused panel.
- Flows where the dialog title changes between steps and should crossfade in place.
When not to use
- Use drawer for long forms or detail panels that keep the page in context.
- Use bottom-sheet for mobile-first secondary tasks with snap heights.
- Use popover for light, non-modal content anchored to a trigger.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "dialog",
"type": "registry:ui",
"title": "Dialog",
"description": "A focused surface for decisions that need attention.",
"categories": [
"disclosure"
],
"dependencies": [
"@radix-ui/react-dialog",
"lucide-react",
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/dialog/dialog.tsx",
"type": "registry:component",
"target": "@components/manicat/dialog/dialog.tsx"
},
{
"path": "registry/components/dialog/dialog.module.css",
"type": "registry:component",
"target": "@components/manicat/dialog/dialog.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/dialog",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/dialog",
"markdown": "https://uiarc.dev/components/dialog/markdown",
"tags": [
"modal",
"overlay"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@/components/manicat/dialog/dialog";
import { Button } from "@/components/manicat/button/button";
export function DialogExample() {
return (
<Dialog>
<DialogTrigger asChild>
<Button>Open dialog</Button>
</DialogTrigger>
<DialogContent title="Rename project" description="The slug updates everywhere it is linked.">
<div className="flex flex-col gap-4">
<label className="text-sm">
Project name
<input className="mt-1 w-full rounded-[var(--radius-control)] border border-(color:--border) bg-(--background) px-3 py-2" defaultValue="arc-library" />
</label>
<div className="flex justify-end gap-2">
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button>Save</Button>
</DialogClose>
</div>
</div>
</DialogContent>
</Dialog>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Destructive confirmation Ask once, say exactly what will be lost, and keep Cancel as the first button.
Destructive confirmation
Ask once, say exactly what will be lost, and keep Cancel as the first button. preview
Live · keyboard ready
destructive-confirmation-ask-once-say-exactly-what-will-be-lost-and-keep-cancel-as-the-first-button-.tsxtsx
Rename dialog A small form in a dialog: the dialog owns focus, closes on Escape and returns focus to the trigger.
Rename dialog
A small form in a dialog: the dialog owns focus, closes on Escape and returns focus to the trigger. preview
Live · keyboard ready
rename-dialog-a-small-form-in-a-dialog-the-dialog-owns-focus-closes-on-escape-and-returns-focus-to-the-trigger-.tsxtsx
"use client";
import { useState } from "react";
import { Button } from "@/components/manicat/button/button";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@/components/manicat/dialog/dialog";
export function DialogRenameExample() {
const [name, setName] = useState("arc-library");
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">Rename project</Button>
</DialogTrigger>
<DialogContent title="Rename project" description="The slug updates everywhere it is linked.">
<div className="flex flex-col gap-4">
<label className="text-sm">
Project name
<input
value={name}
onChange={(event) => setName(event.target.value)}
className="mt-1 w-full rounded-[var(--radius-control)] border border-(color:--border) bg-(--background) px-3 py-2"
/>
</label>
<div className="flex justify-end gap-2">
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button>Save</Button>
</DialogClose>
</div>
</div>
</DialogContent>
</Dialog>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| Escape | Closes the dialog and returns focus to the trigger. |
| Tab / Shift+Tab | Cycles focus within the dialog. |
Accessibility
- Radix renders role="dialog" with aria-modal, traps focus, and restores it to the trigger on close.
- title and description are wired to aria-labelledby and aria-describedby.
- The close button carries aria-label="Close dialog".
Motion
- The overlay fades while the panel rises 8px and scales from 0.96 on a smooth spring; closing is shorter and retargets from the current state.
- Title and description changes rise in with a soft blur.
- Reduced motion uses a plain opacity fade, including the CSS keyframe fallback for bare Radix roots.
Responsive behavior
- The panel is min(100vw minus a 32px gutter, 440px) wide and centered, so it fits phones without extra CSS.
- Height caps at the viewport minus a gutter and the panel scrolls beyond that.
Performance
- The overlay uses a 7px backdrop blur, which can cost frames on low-end devices over busy pages.
- Content renders in a portal only while open and unmounts after the exit animation.
Notes for AI
- Use for decisions that must interrupt: confirmations, short forms. Use drawer for side panels, bottom-sheet for mobile-first secondary tasks, popover for light non-modal content.
- Always compose Dialog > DialogTrigger + DialogContent. Wrap your own buttons with asChild.