Dialog
Modal dialog with backdrop blur, scroll lock, keyboard dismiss, and multiple size options — perfect for confirmations, forms, and alerts.
Preview
Confirmation dialog
import { Dialog, DialogTitle, DialogDescription, DialogActions } from '@/registry/ui-kit/tailwind-dialog/react'
import { useState } from 'react'
function Example() {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Delete project</button>
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogTitle>Delete project</DialogTitle>
<DialogDescription>Are you sure? This action cannot be undone.</DialogDescription>
<DialogActions>
<button onClick={() => setOpen(false)}>Cancel</button>
<button onClick={() => setOpen(false)}>Delete</button>
</DialogActions>
</Dialog>
</>
)
}Component API
| Prop | Default | Description |
|---|---|---|
open | false | Whether the dialog is visible. |
onClose | — | Callback fired when the dialog should close. |
size | "md" | Max-width of the dialog panel. |
DialogTitle | — | Heading rendered inside the dialog panel. |
DialogDescription | — | Secondary text below the title. |
DialogBody | — | Generic container for dialog content such as forms. |
DialogActions | — | Right-aligned row of action buttons. |
Source Code
"use client";
import React, { useEffect, useRef } from "react";
const sizeMap: Record<string, string> = {
xs: "max-w-xs",
sm: "max-w-sm",
md: "max-w-md",
lg: "max-w-lg",
xl: "max-w-xl",
};
/* ── Dialog ─────────────────────────────────────────── */
export function Dialog({
open,
onClose,
size = "md",
className = "",
children,
}: {
open: boolean;
onClose: (value: false) => void;
size?: "xs" | "sm" | "md" | "lg" | "xl";
className?: string;
children: React.ReactNode;
}) {
const panelRef = useRef<HTMLDivElement>(null);
// Escape key
useEffect(() => {
function handleKey(e: KeyboardEvent) {
if (e.key === "Escape") onClose(false);
}
if (open) {
document.addEventListener("keydown", handleKey);
return () => document.removeEventListener("keydown", handleKey);
}
}, [open, onClose]);
// Body scroll lock
useEffect(() => {
if (open) {
const original = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = original;
};
}
}, [open]);
// Focus panel on open
useEffect(() => {
if (open && panelRef.current) {
panelRef.current.focus();
}
}, [open]);
if (!open) return null;
const sizeClass = sizeMap[size] || sizeMap.md;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Backdrop */}
<div
className="fixed inset-0 bg-zinc-950/25 backdrop-blur-sm dark:bg-zinc-950/50"
aria-hidden="true"
onClick={() => onClose(false)}
/>
{/* Panel */}
<div
ref={panelRef}
tabIndex={-1}
role="dialog"
aria-modal="true"
className={`relative w-full ${sizeClass} rounded-xl border border-zinc-200 bg-white p-6 shadow-lg outline-none dark:border-zinc-700 dark:bg-zinc-900 ${className}`}
>
{children}
</div>
</div>
);
}
/* ── DialogTitle ────────────────────────────────────── */
export function DialogTitle({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<h3 className={`text-base font-semibold text-zinc-900 dark:text-zinc-100 ${className}`}>
{children}
</h3>
);
}
/* ── DialogDescription ──────────────────────────────── */
export function DialogDescription({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<p className={`text-sm text-zinc-500 dark:text-zinc-400 ${className}`}>
{children}
</p>
);
}
/* ── DialogBody ─────────────────────────────────────── */
export function DialogBody({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
return <div className={className}>{children}</div>;
}
/* ── DialogActions ──────────────────────────────────── */
export function DialogActions({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div className={`flex items-center justify-end gap-3 pt-4 ${className}`}>
{children}
</div>
);
}