Skip to content
Dethink Components

ComponentsDialog

Dialog

Open a focused window above the page for a task or confirmation.

On this page

Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.

View registry files

Import the component into your page or component file.

Usage
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
examples/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
examples/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
examples/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
examples/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.

Dialog anatomy
PropWhat it doesDefault
open / defaultOpen / onOpenChangeboolean / boolean / (open) => voidUse 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 — dismissiblebooleanWhether clicking the backdrop closes the dialog.false
DialogContent — keyboardDismissDisabledbooleanPrevents the Escape key from closing the dialog.false
DialogContent — scrollBehavior"inside" | "outside"Whether long content scrolls within the panel or the page."inside"
DialogContent — showCloseButtonbooleanShows a close button in the corner.false
DialogHeader / DialogTitle / DialogDescription / DialogFootersection componentsBuild the dialog header and footer. The title and description tell screen readers what the dialog is for.Not set
AlertDialog additions
PropWhat it doesDefault
AlertDialogsame as DialogA 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