ComponentsDialog
Dialog
Open a focused window above the page for a task or confirmation.
On this page
Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.
View registry files- React Aria
(react-aria)Accessibility hooks and overlay utilities used by the implementation.
- React Aria Components
(react-aria-components)Accessible interaction primitives, including keyboard behavior, focus handling, and ARIA semantics.
Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.
Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.
Import the component into your page or component file.
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@dethink/components";
export function Example() {
return (
<Dialog>
<DialogTrigger>View details</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Project details</DialogTitle>
<DialogDescription>Review your project settings.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose>Done</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Try the examples, then open the code to use them in your app. Open a dialog and Tab around — focus stays inside until it closes.
Basic
Use DialogTrigger to open the dialog and DialogClose to close it. Always include a DialogTitle.
Show sourceexamples/dialog/basic.tsx
"use client";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@dethink/components";
export function DialogBasic() {
return (
<div className="flex justify-center">
<Dialog>
<DialogTrigger>Workspace settings</DialogTrigger>
<DialogContent dismissible size="sm">
<DialogHeader>
<DialogTitle>Workspace settings</DialogTitle>
<DialogDescription>
Changes apply to every dashboard in this workspace.
</DialogDescription>
</DialogHeader>
<div className="px-[var(--dt-space-6)] py-[var(--dt-space-3)] text-sm">
Focus is trapped inside; Escape or the backdrop dismisses, and focus
returns to the trigger.
</div>
<DialogFooter>
<DialogClose variant="outline">Cancel</DialogClose>
<DialogClose variant="solid">Save changes</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}Alert dialog
Ask users to confirm an action. Clicking outside this dialog does not close it.
Show sourceexamples/dialog/alert.tsx
"use client";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@dethink/components";
export function DialogAlert() {
return (
<div className="flex justify-center">
<AlertDialog>
<AlertDialogTrigger variant="destructive">
Revoke all sessions
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Revoke all sessions?</AlertDialogTitle>
<AlertDialogDescription>
Every device is signed out immediately, including this one.
AlertDialog blocks backdrop dismissal so the choice is explicit.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Keep sessions</AlertDialogCancel>
<AlertDialogAction variant="destructive">
Revoke everything
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}Examples that combine components for common tasks.
Multi-step wizard
Split a task into three steps. Back and Continue move between steps; closing the dialog resets the form.
Show sourceexamples/dialog/recipe-wizard.tsx
"use client";
import { useState } from "react";
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
Field,
FieldControl,
FieldLabel,
Input,
Select,
SelectItem,
} from "@dethink/components";
const steps = ["Name", "Region", "Review"] as const;
/**
* A controlled Dialog hosting a multi-step flow: the dialog stays open
* across steps, Back/Continue drive the index, and the flow resets whenever
* the dialog closes so reopening starts clean.
*/
export function DialogRecipeWizard() {
const [open, setOpen] = useState(false);
const [step, setStep] = useState(0);
const [name, setName] = useState("");
const [region, setRegion] = useState("");
const isLast = step === steps.length - 1;
return (
<div className="flex justify-center">
<Dialog
open={open}
onOpenChange={(next) => {
setOpen(next);
if (!next) {
setStep(0);
}
}}
>
<DialogTrigger>New project…</DialogTrigger>
<DialogContent size="sm">
<DialogHeader>
<DialogTitle>New project — {steps[step]}</DialogTitle>
<DialogDescription>
Step {step + 1} of {steps.length}
</DialogDescription>
</DialogHeader>
<div className="px-[var(--dt-space-6)] py-[var(--dt-space-3)]">
{step === 0 ? (
<Field id="wiz-name">
<FieldLabel>Project name</FieldLabel>
<FieldControl asChild>
<Input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="apollo"
/>
</FieldControl>
</Field>
) : step === 1 ? (
<Select
label="Region"
placeholder="Choose a region"
value={region || undefined}
onValueChange={setRegion}
>
<SelectItem value="us-east">US East</SelectItem>
<SelectItem value="eu-west">EU West</SelectItem>
</Select>
) : (
<dl className="space-y-1 text-sm">
<div className="flex justify-between">
<dt className="text-muted-foreground">Name</dt>
<dd>{name || "—"}</dd>
</div>
<div className="flex justify-between">
<dt className="text-muted-foreground">Region</dt>
<dd>{region || "—"}</dd>
</div>
</dl>
)}
</div>
<DialogFooter>
<Button
variant="outline"
disabled={step === 0}
onClick={() => setStep(step - 1)}
>
Back
</Button>
<Button
onClick={() => {
if (isLast) {
setOpen(false);
setStep(0);
} else {
setStep(step + 1);
}
}}
>
{isLast ? "Create project" : "Continue"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}Type-to-confirm deletion
Require users to type the project name before enabling Delete.
Show sourceexamples/dialog/recipe-type-to-confirm.tsx
"use client";
import { useState } from "react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
Field,
FieldControl,
FieldLabel,
Input,
} from "@dethink/components";
const PROJECT = "apollo-prod";
/**
* Destructive confirmation that cannot be clicked through on autopilot:
* the action stays disabled until the typed name matches exactly, and
* closing resets the input.
*/
export function DialogRecipeTypeToConfirm() {
const [typed, setTyped] = useState("");
const confirmed = typed === PROJECT;
return (
<div className="flex justify-center">
<AlertDialog onOpenChange={(open) => !open && setTyped("")}>
<AlertDialogTrigger variant="destructive">
Delete project…
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete {PROJECT}?</AlertDialogTitle>
<AlertDialogDescription>
This permanently deletes the project, its deployments, and all
request logs. Type the project name to confirm.
</AlertDialogDescription>
</AlertDialogHeader>
<div className="px-[var(--dt-space-6)] py-[var(--dt-space-2)]">
<Field id="del-confirm">
<FieldLabel>
Type <span className="font-mono">{PROJECT}</span> to continue
</FieldLabel>
<FieldControl asChild>
<Input
autoComplete="off"
value={typed}
onChange={(event) => setTyped(event.target.value)}
/>
</FieldControl>
</Field>
</div>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive" isDisabled={!confirmed}>
Delete forever
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}Set open state on Dialog. Set width and closing behavior on DialogContent.
| Prop | What it does | Default |
|---|---|---|
open / defaultOpen / onOpenChangeboolean / boolean / (open) => void | Use open with onOpenChange to manage the dialog, or defaultOpen to set its starting state. | Not set |
DialogTrigger / DialogClosevariant + size (Button API) | Buttons that open and close the dialog; both accept the Button variant and size props. | Not set |
DialogContent — size"sm" | "md" | "lg" | "xl" | "full" | Sets the dialog width. | "md" |
DialogContent — dismissibleboolean | Whether clicking the backdrop closes the dialog. | false |
DialogContent — keyboardDismissDisabledboolean | Prevents the Escape key from closing the dialog. | false |
DialogContent — scrollBehavior"inside" | "outside" | Whether long content scrolls within the panel or the page. | "inside" |
DialogContent — showCloseButtonboolean | Shows a close button in the corner. | false |
DialogHeader / DialogTitle / DialogDescription / DialogFootersection components | Build the dialog header and footer. The title and description tell screen readers what the dialog is for. | Not set |
| Prop | What it does | Default |
|---|---|---|
AlertDialogsame as Dialog | A confirmation dialog. Clicking outside it does not close it. | Not set |
AlertDialogAction / AlertDialogCancelvariant + size (Button API) | Confirm and cancel buttons; Action supports isDisabled for gated confirmation. | Not set |