DialogDesign system
ComponentProduct

Dialog

Modal dialog with a framed panel, focus management, Escape dismissal, and a scrollable overlay.

npx context-ds add dialog
In the product

Usage

Use it for

Confirmations, short forms, pickers. Title h5, body padded 20px, actions right-aligned over a light line.

Avoid

  • Centered dialogs
  • Dialogs for long workflows (use a page)

Dependencies: surface, @radix-ui/react-dialog, lucide-react

Example

examples/dialog.tsx
"use client";import { useState } from "react";import { Button } from "@/components/ui/button";import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";import { Input } from "@/components/ui/input";import { Field } from "@/components/ui/field";import { SecretField } from "@/components/ui/secret-field";export function Product() {  const [open, setOpen] = useState(false);  const [name, setName] = useState("");  const [created, setCreated] = useState("");  return (    <div className="grid w-full max-w-sm gap-4">      <Dialog open={open} onOpenChange={setOpen}>        <DialogTrigger asChild><Button variant="brand">Create key</Button></DialogTrigger>        <DialogContent>          <form onSubmit={(event) => { event.preventDefault(); if (!name.trim()) return; setCreated(name.trim()); setOpen(false); }}>            <DialogHeader><DialogTitle>Create an API key</DialogTitle><DialogDescription>Keys work for every endpoint on your plan.</DialogDescription></DialogHeader>            <DialogBody><Field label="Name" htmlFor="key-name" help="Only you see this." required><Input id="key-name" value={name} onChange={(event) => setName(event.target.value)} placeholder="Production" /></Field></DialogBody>            <DialogFooter><DialogClose asChild><Button variant="outline">Cancel</Button></DialogClose><Button type="submit" variant="brand" disabled={!name.trim()}>Create</Button></DialogFooter>          </form>        </DialogContent>      </Dialog>      {created && <div className="grid gap-2"><p role="status" className="text-body-sm">{created} key created in this example.</p><SecretField secret="ctx_example_9f2c41b7e0a84d6c" /></div>}    </div>  );}

Source

components/ui/dialog.tsx
"use client";import * as React from "react";import * as DialogPrimitive from "@radix-ui/react-dialog";import { X } from "lucide-react";import { usePortalContainer } from "@/lib/surface";import { cn } from "@/lib/utils";// The dashboard's dialog: a white panel in a 6px frame that floats on a hairline and a soft shadow, rising 4px as it// fades in. Radix adds the focus trap, scroll lock and Escape.function Dialog(props: React.ComponentProps<typeof DialogPrimitive.Root>) {  return <DialogPrimitive.Root data-slot="dialog" {...props} />;}function DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger>) {  return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;}function DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>) {  return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;}function DialogPortal(props: React.ComponentProps<typeof DialogPrimitive.Portal>) {  const container = usePortalContainer();  return <DialogPrimitive.Portal data-slot="dialog-portal" container={container} {...props} />;}function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {  return <DialogPrimitive.Overlay data-slot="dialog-overlay" className={cn("fixed inset-0 z-50 flex items-start justify-center overflow-y-auto overscroll-contain bg-scrim px-4 pt-[12vh] pb-6 max-md:pt-4", className)} {...props} />;}const WIDTHS = { md: "max-w-lg", lg: "max-w-3xl", xl: "max-w-5xl", "2xl": "max-w-6xl" } as const;function DialogContent({  className,  children,  size = "md",  showCloseButton = true,  ...props}: React.ComponentProps<typeof DialogPrimitive.Content> & { size?: keyof typeof WIDTHS; showCloseButton?: boolean }) {  return (    <DialogPortal>      <DialogOverlay>        <DialogPrimitive.Content          data-slot="dialog-content"          className={cn(            "relative z-50 w-full shrink-0 animate-content-in rounded-shell bg-surface p-(--ds-space-frame) shadow-floating outline-none",            WIDTHS[size],            className,          )}          {...props}        >          <div className="relative rounded-shell bg-surface">            {children}            {showCloseButton && (            <DialogPrimitive.Close              aria-label="Close"              className="absolute top-3.5 right-3 grid size-8 place-items-center rounded-control text-fg-subtle transition-colors duration-150 ease-out outline-none hover:bg-wash-hover hover:text-fg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus-ring [&_svg]:size-4"            >              <X strokeWidth={1.5} />            </DialogPrimitive.Close>            )}          </div>        </DialogPrimitive.Content>      </DialogOverlay>    </DialogPortal>  );}function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {  return <div data-slot="dialog-header" className={cn("flex flex-col gap-1.5 px-5 pt-5 pr-15", className)} {...props} />;}function DialogBody({ className, ...props }: React.ComponentProps<"div">) {  return <div data-slot="dialog-body" className={cn("px-5 pt-5 pb-5 text-body-sm text-fg", className)} {...props} />;}function DialogFooter({ className, divider = true, ...props }: React.ComponentProps<"div"> & { divider?: boolean }) {  return <div data-slot="dialog-footer" className={cn("flex items-center justify-end gap-2 px-5", divider ? "border-t border-line-subtle py-3.5" : "pb-5", className)} {...props} />;}function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {  return <DialogPrimitive.Title data-slot="dialog-title" className={cn("text-h5 leading-snug font-normal tracking-normal text-fg", className)} {...props} />;}function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {  return <DialogPrimitive.Description data-slot="dialog-description" className={cn("text-body-sm text-fg-muted", className)} {...props} />;}export { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger };

Reference: context-dashboard c0ddf7a src/components/ui/dialog.tsx.