Core Component
Modal
Dialog overlay for confirmations, settings, and focused tasks that require attention before continuing.
Preview
Delete conversation?
This will permanently delete the conversation and all artifacts. This action cannot be undone.
Source
Full component implementation using the design system tokens.
"use client";
import { useEffect, useRef } from "react";
import { X } from "lucide-react";
export function DSModal({
open = false,
onClose,
title,
children,
footer,
}: {
open?: boolean;
onClose?: () => void;
title: string;
children: React.ReactNode;
footer?: React.ReactNode;
}) {
const overlayRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape" && onClose) onClose();
};
if (open) {
document.addEventListener("keydown", handleKeyDown);
document.body.style.overflow = "hidden";
}
return () => {
document.removeEventListener("keydown", handleKeyDown);
document.body.style.overflow = "";
};
}, [open, onClose]);
if (!open) return null;
return (
<div
ref={overlayRef}
className="fixed inset-0 z-50 flex items-center justify-center p-4"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<div
className="absolute inset-0 bg-overlay/40 backdrop-blur-sm"
onClick={onClose}
/>
<div className="relative w-full max-w-md bg-background border border-overlay/10 rounded-2xl shadow-xl">
<div className="flex items-center justify-between px-6 py-4 border-b border-overlay/5">
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
{title}
</h2>
{onClose && (
<button
onClick={onClose}
className="p-1 text-tertiary hover:text-foreground transition-colors rounded-lg hover:bg-overlay/5"
aria-label="Close dialog"
>
<X className="w-5 h-5" />
</button>
)}
</div>
<div className="px-6 py-5">{children}</div>
{footer && (
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-overlay/5">
{footer}
</div>
)}
</div>
</div>
);
}Props
All available props with types and defaults.
| Prop | Type | Default | Description |
|---|---|---|---|
title* | string | — | Dialog title |
children* | ReactNode | — | Dialog body content |
open | boolean | false | Controls visibility |
onClose | () => void | — | Close callback — enables backdrop click and Escape key |
footer | ReactNode | — | Footer area — typically action buttons |
Variants
Confirmation
Destructive action confirmation with cancel/confirm buttons.
Delete conversation?
This will permanently delete the conversation and all artifacts.
<DSModal
open={true}
onClose={close}
title="Delete conversation?"
footer={
<>
<DSButton variant="ghost" onClick={close}>Cancel</DSButton>
<DSButton variant="destructive">Delete</DSButton>
</>
}
>
<p>This will permanently delete the conversation and all artifacts.</p>
</DSModal>Settings
Form-based dialog for model configuration.
Model Settings
<DSModal
open={true}
onClose={close}
title="Model Settings"
footer={<DSButton>Save changes</DSButton>}
>
<DSInput label="Temperature" placeholder="0.7" />
<DSInput label="Max tokens" placeholder="4096" />
</DSModal>Prompt Guide
Prompt Guide — Modal
Use for
- Destructive action confirmations (delete conversation, clear history)
- Settings dialogs (model config, API keys)
- Focused input tasks (rename, export options)
- Onboarding flows and first-run setup
Don't use for
- Simple confirmations — use Toast instead
- Content display — use Artifact Card for AI-generated content
- Frequent actions — modals interrupt flow; use inline UI
AI Context
Modals in AI products are for high-stakes moments: 'Delete this conversation?' (destructive), 'Configure API key' (setup), 'Export chat' (focused task). Always provide Escape key dismissal and backdrop click. Focus traps inside the modal. Return focus to the trigger when closed.