Dialog
A focused task with accessible modal behavior and guarded dismissal.
Import Dialog and ConfirmDialog from @commenda/pathfinder/dialog.
API reference
| Prop | Type | Behavior |
|---|---|---|
titleRequired | string | Required accessible heading |
description | ReactNode | Optional supporting text |
trigger | ReactElement<any, string | React.JSXElementConstructor<any>> | Optional ref-forwarding button |
childrenRequired | ReactNode | Form or focused task body |
size | "sm"|"md"|"lg"|"xl" | lg; 400 / 480 / 600 / 800px, viewport-clamped |
footerConfig | OverlayFooterConfigView fields | Optional footer |
dismissConfig | OverlayDismissConfigView fields | Outside dismissal enabled; guard may be async |
focusConfig | OverlayFocusConfigView fields | First body control, then popup; returns to trigger |
className | string | Popup classes |
openConditional | boolean | — |
onOpenChangeConditional | (open: boolean) => void | — |
defaultOpen | boolean | — |
dismissConfigObject fields
| Field | Type | Requirement |
|---|---|---|
outsidePress | boolean | Optional |
beforeClose | ((reason: OverlayCloseReason) => boolean | Promise<boolean>) | Optional |
onError | ((error: unknown) => void) | Optional |
focusConfigObject fields
| Field | Type | Requirement |
|---|---|---|
initialFocusRef | RefObject<HTMLElement | null> | Optional |
returnFocusRef | RefObject<HTMLElement | null> | Optional |
open with onOpenChange is controlled; otherwise use defaultOpen. Primary actions never imply success or automatically close the surface. Close it after the application completes its work.
footerConfig accepts primaryAction, secondaryAction, leadingContent, and cancelLabel. Actions have label, onClick, native type/form, disabled, disabledReason, loadingConfig, destructive, and icons. Without a secondary action the footer renders Cancel. A footer is absent unless configured. Submit uses native form IDs; IDs must be unique.
Sizes
Confirmation
ConfirmDialog requires title, description, and confirmAction. It also accepts the same open state, trigger, focus config, a cancelLabel, and dismissal guard. It uses alert-dialog semantics, focuses Cancel, has no close icon, and ignores outside presses. A loading confirmation blocks dismissal until the application completes or reports an error.
Scrolling and nested controls
Body scrolling adds header/footer hairlines. Select menus and popovers remain inside the focus boundary through their portals. Escape closes the innermost surface first.
beforeClose(reason) receives escape, outside, close, cancel, or trigger and returns a boolean or promise. False keeps the dialog open; failure announces an error. Duplicate requests are ignored while pending; a decision from an earlier open cycle cannot close a reopened dialog. Setting controlled open=false is an explicit application decision and bypasses the guard. Read the overlay rules.
Design rules
Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.
Dialog and confirmation
- DIALOG-001: One focused task. sm/md/lg/xl are 400/480/600/800px; default lg. Radius 12, 0.5px pearl-300 edge, modal shadow. Backdrop pearl-1000 at 10%, no blur. Full-screen and multi-step work belongs on a page.
- DIALOG-002: Heading/15 title, Copy/13 description. Header padding 20px top, 24px start, 16px end/bottom; text inset 4px. Body 24px horizontal, 4px top, 16px bottom. Footer 24px horizontal, 16px top, 20px bottom. Body scrolls independently; hairlines appear after scrolling. Cancel then action-named primary at 32px; optional leading content.
- DIALOG-003: Initial focus enters the body, focus remains contained, and returns to the trigger. Explicit refs override defaults. Escape affects the innermost surface. Async guards block duplicate requests and ignore stale cycles; callers own mutation success and controlled closure.
- CONFIRM-001: Alertdialog with question and consequence. Focus Cancel; no close icon or outside dismissal. Destructive action is named, not “Yes”.
- CONFIRM-002: Clicking is not success. Loading and failure remain app-owned; a loading confirmation cannot be dismissed by its controls.
Motion and edge cases
Occasional overlays use motion for spatial consistency. Motion paths and timings follow Kakashi: Dialog fades and moves from 95% scale with a 2% vertical offset in 200ms both ways; Popover fades from 95% scale and an 8px offset toward its trigger in 150ms both ways. Popover direction follows the resolved placement after collision handling. Right Sheet slides in for 500ms and out for 300ms; bottom Sheet slides for 500ms both ways. Sheet panels do not fade during normal motion, and modal backdrops follow the panel duration.
Pathfinder retains its foundation ease-out curve for Dialog/Popover and drawer curve (0.32,0.72,0,1) for Sheets. These are deliberate curve choices; this is not a copy of Kakashi’s legacy Radix keyframe implementation. Base UI continues to own presence, focus and dismissal. Transitions use a single transform value plus opacity where relevant, so Tailwind v4’s separate scale/translate properties cannot bypass interpolation. Closing or reopening mid-transition reverses from the current rendered position.
Keyboard opening and dismissal remain immediate, including Close and Cancel activation. Reduced motion removes scale and translation: Dialog/Popover keep their fade, while Sheets change position immediately and their modal backdrop can fade. Drag writes only the popup transform, never layout or inherited animation variables. Programmatic openings animate by default; explicit keyboard interactions suppress motion.