Dialog
Modal dialog framed with corner-bracket reticles and a hard offset shadow.
[ interactive — use the trigger to open ]
"use client";import { Button } from "@/registry/okibi/ui/button";import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/registry/okibi/ui/dialog";export default function DialogDemo() { return ( <Dialog> <DialogTrigger asChild> <Button variant="outline-tech">Open Console</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Sector Override</DialogTitle> <DialogDescription> Confirm manual override of perimeter ICE on SEC // 0x4A2. </DialogDescription> </DialogHeader> <DialogFooter> <DialogClose asChild> <Button variant="ghost">Abort</Button> </DialogClose> <DialogClose asChild> <Button variant="signal">Deploy</Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog> );}npx shadcn@latest add https://okibi.cndr.dev/r/dialog.jsonInstallation
Install the theme first, then add the component:
npx shadcn@latest add https://okibi.cndr.dev/r/dialog.jsonInstall the required dependencies:
npm install @radix-ui/react-dialog lucide-reactCopy the source from the Code tab above into components/ui/dialog.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.
Usage
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
<Dialog>
<DialogTrigger>Edit Channel</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Channel</DialogTitle>
<DialogDescription>Re-key the relay on SEC // 0x4A2.</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>Anatomy
<Dialog>
<DialogTrigger>Edit Channel</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Channel</DialogTitle>
<DialogDescription>Re-key the relay on SEC // 0x4A2.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>Examples
Form
Drop fielded rows into the body and dock the actions in DialogFooter; DialogClose dismisses on cancel or save.
[ interactive — use the trigger to open ]
"use client";import { Button } from "@/registry/okibi/ui/button";import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/registry/okibi/ui/dialog";import { Input } from "@/registry/okibi/ui/input";import { Label } from "@/registry/okibi/ui/label";export default function DialogFormDemo() { return ( <Dialog> <DialogTrigger asChild> <Button variant="outline-tech">Edit Channel</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Edit Channel</DialogTitle> <DialogDescription> Re-key the relay on SEC // 0x4A2. Changes commit on save. </DialogDescription> </DialogHeader> <div className="grid gap-4"> <div className="grid gap-1.5"> <Label htmlFor="dialog-form-callsign">Call sign</Label> <Input id="dialog-form-callsign" defaultValue="VANTA-7" /> </div> <div className="grid gap-1.5"> <Label htmlFor="dialog-form-frequency">Frequency</Label> <Input id="dialog-form-frequency" defaultValue="148.500 MHz" /> </div> </div> <DialogFooter> <DialogClose asChild> <Button variant="ghost">Cancel</Button> </DialogClose> <DialogClose asChild> <Button variant="signal">Save</Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog> );}Destructive confirm
A confirm dialog for an irreversible action: a hazard-variant confirm button beside a DialogClose cancel.
[ interactive — use the trigger to open ]
"use client";import { Button } from "@/registry/okibi/ui/button";import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/registry/okibi/ui/dialog";export default function DialogDestructiveDemo() { return ( <Dialog> <DialogTrigger asChild> <Button variant="outline-tech">Purge Node</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Purge Node 0x4A2</DialogTitle> <DialogDescription> This wipes the relay and severs all linked channels. This action is irreversible — there is no recovery once committed. </DialogDescription> </DialogHeader> <DialogFooter> <DialogClose asChild> <Button variant="ghost">Cancel</Button> </DialogClose> <DialogClose asChild> <Button variant="destructive-hazard">Purge</Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog> );}Scrolling content
Long bodies scroll inside the panel while the header and footer stay pinned.
[ interactive — use the trigger to open ]
"use client";import { Button } from "@/registry/okibi/ui/button";import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/registry/okibi/ui/dialog";const CLAUSES = [ "Access to SEC // 0x4A2 is granted under provisional clearance only.", "All relay traffic is logged, timestamped, and mirrored to cold storage.", "Override tokens expire after a single perimeter cycle and do not renew.", "Manual ICE suspension voids the active warranty on linked subsystems.", "Operators assume full liability for downstream channel corruption.", "Re-keying a live relay forces a hard re-sync of all bound endpoints.", "Telemetry may be throttled without notice during contested windows.", "Decommissioned nodes are purged on the next maintenance interval.", "Signal spoofing on a sealed sector triggers immediate lockout.", "Continued use constitutes acceptance of the full operating protocol.",];export default function DialogScrollDemo() { return ( <Dialog> <DialogTrigger asChild> <Button variant="outline-tech">Review Protocol</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Operating Protocol</DialogTitle> <DialogDescription> Scroll the full terms before committing to perimeter access. </DialogDescription> </DialogHeader> <div className="max-h-64 overflow-y-auto border-2 border-border-strong bg-background p-4"> <ol className="grid gap-3 text-sm text-muted-foreground"> {CLAUSES.map((clause, i) => ( <li key={i} className="flex gap-2"> <span className="shrink-0 font-display text-signal-spark tabular-nums"> {String(i + 1).padStart(2, "0")} </span> <span>{clause}</span> </li> ))} </ol> </div> <DialogFooter> <DialogClose asChild> <Button variant="ghost">Decline</Button> </DialogClose> <DialogClose asChild> <Button variant="signal">Accept</Button> </DialogClose> </DialogFooter> </DialogContent> </Dialog> );}Controlled
Own the open state with open + onOpenChange — open from an out-of-tree button and close programmatically after a confirm action.
"use client";import * as React from "react";import { Button } from "@/registry/okibi/ui/button";import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,} from "@/registry/okibi/ui/dialog";export default function DialogControlledDemo() { // Drive open state yourself with `open` + `onOpenChange` — here a second, // out-of-tree button opens the same dialog (no `DialogTrigger`), and a // confirm action both runs side effects and closes the panel. const [open, setOpen] = React.useState(false); const [armed, setArmed] = React.useState(false); return ( <div className="flex flex-col items-start gap-3"> <Button variant="outline-tech" onClick={() => setOpen(true)}> Arm Sequence </Button> <span className="font-mono text-xs uppercase tracking-grid-wide text-muted-foreground"> {armed ? "STATE // ARMED" : "STATE // SAFE"} </span> <Dialog open={open} onOpenChange={setOpen}> <DialogContent> <DialogHeader> <DialogTitle>Arm Launch Sequence</DialogTitle> <DialogDescription> Open state is owned by the parent — confirm to flip the relay and close the dialog programmatically. </DialogDescription> </DialogHeader> <DialogFooter> <DialogClose asChild> <Button variant="ghost">Cancel</Button> </DialogClose> <Button variant="signal" onClick={() => { setArmed(true); setOpen(false); }} > Confirm Arm </Button> </DialogFooter> </DialogContent> </Dialog> </div> );}Accessibility
- Built on Radix Dialog, so the focus trap, focus restore on close,
Esc-to-dismiss, and overlay-click dismiss come for free. - A
DialogTitleis required — it labels the dialog viaaria-labelledby; Radix warns when it's missing. - Include
DialogDescriptionto wire the body viaaria-describedby; if you omit it, passaria-describedby={undefined}onDialogContentto silence the warning. - The four corner-bracket reticles are decorative — they're
aria-hiddenandpointer-events-none. - The close (X) control rendered by
showCloseButtoncarries ansr-only'Close' label and a 44px pointer hit area (an invisiblebefore:-inset-2overlay) per WCAG 2.5.5.
Guidelines
Do
- Always render a
DialogTitle, even if you visually hide it, so the modal is named. - Keep
DialogDescriptionin the body to give the dialog anaria-describedbytarget. - Reserve right-edge gutter for a long
DialogTitleso it doesn't run under the close button.
Don't
- Don't drop
DialogTitle— the dialog loses its accessible name and Radix warns. - Don't set
showCloseButton={false}without providing another obvious, labeled dismiss. - Don't soften the hard offset shadow or the
border-border-strongdouble border — the sharp HUD modal is intentional.