Dialog
Purpose
A Dialog is a modal overlay that interrupts the current workflow to focus the user's attention on a specific task or information. Use a Dialog for: confirming destructive actions, collecting a small set of inputs before proceeding, displaying critical information that requires acknowledgment, or running a multi-step creation wizard. Do NOT use a Dialog for navigation — use routing instead. Do NOT use a Dialog for non-critical information that the user can safely ignore — use a Toast or an inline callout. Do NOT use a Dialog for content that needs to appear alongside and interact with the page — use a Drawer or a Sheet instead.
Anatomy
A Dialog is composed of two structural layers:
Overlay (backdrop):
- A full-viewport fixed element behind the dialog panel. Background is
--force-color-bg-overlay(50% black). Applied atz-index: --force-z-index-modal-backdrop(400). Its purpose is to visually de-emphasize the page content and prevent interaction with it.
Dialog panel (content):
- Dialog Header: contains the title and optionally a subtitle/description.
- Dialog Title (required): a concise heading describing the dialog's purpose. Rendered as a visually prominent heading element.
- Dialog Description (optional): one or two sentences of supporting context below the title.
- Close button: an icon-only button (
×) positioned absolutely in the top-right corner of the panel. Always present unless the dialog requires a deliberate choice (no escape route) — for example, a license agreement where the user must explicitly accept or reject. Clicking the close button dismisses the dialog without taking action. - Dialog body / content area: the primary content region. Contains the form fields, confirmation message, information, or wizard step content.
- Dialog Footer: a row of action buttons. Right-aligned by default. Contains the primary action button (rightmost) and secondary/cancel button (to its left). For destructive confirmations, the primary action is a
primary / dangerButton.
Variants
Dialogs are differentiated by size. All sizes share the same structural anatomy.
sm (max-width ~384px / Tailwind sm:max-w-sm)
Use for simple confirmations that require only a title, a one-sentence message, and two buttons ("Cancel" and "Delete"). No form fields. This is the correct size for destructive confirmation dialogs.
md (max-width ~448px / Tailwind sm:max-w-md)
Use for single-field or two-field input dialogs. Examples: "Rename dataset," "Add tag," "Invite user by email."
lg (max-width ~512px / Tailwind sm:max-w-lg)
Use for short forms (three to five fields). Examples: "Create new environment," "Edit connection settings."
xl (max-width ~576px / Tailwind sm:max-w-xl)
Use for forms with moderate complexity or when content requires more horizontal space. Examples: "Configure replication policy."
2xl (max-width ~672px / Tailwind sm:max-w-2xl, this is the default)
The default fallback size. Use when you have not yet determined the appropriate size, or for general-purpose dialogs. Review and downsize to a more appropriate variant before shipping.
3xl through 7xl
Use for wizard-style dialogs with a sidebar step list, or for dialogs that contain a data table or rich content. Prefer the Drawer component for persistent side panels. Use these large sizes sparingly — a dialog that requires 7xl width is likely better suited as a full-page route.
States
Closed: The dialog and overlay are not rendered, or are rendered with display: none. No interaction.
Opening (enter animation): The dialog panel fades in and scales up from 95% to 100% (zoom-in-95 → zoom-in-100, fade-in-0 → fade-in-100). Duration: --force-duration-normal (250ms) with --force-easing-decelerate. The overlay fades in concurrently.
Open: The dialog is fully visible and interactive. The body scroll is locked — the page beneath the overlay does not scroll while the dialog is open.
Closing (exit animation): The reverse of the opening animation. fade-out-0, zoom-out-95. Duration: --force-duration-normal with --force-easing-accelerate.
Overlay interaction: Clicking the overlay (outside the dialog panel) dismisses the dialog by default. This behavior MUST be disabled for dialogs that require a deliberate choice (e.g., unsaved changes warning, license acceptance). When disabling overlay-dismiss, add a visual cue such as a brief shake animation on the dialog panel.
Behavior
- Focus management: When the dialog opens, focus is moved to the first focusable element inside the panel. Typically this is the first form input, or the primary action button if there are no inputs. Focus MUST move to the dialog — do not leave focus on the trigger element.
- Focus trap: While the dialog is open, keyboard focus is trapped within the dialog panel. Tab cycles through focusable elements inside the dialog and wraps around. Focus MUST NOT escape to the page beneath the overlay.
- Closing with Escape: Pressing Escape always closes the dialog (unless the dialog explicitly disables this, as in a mandatory decision prompt). When the dialog closes, focus returns to the element that originally triggered it.
- Scroll within dialog: If the dialog body content is taller than the viewport allows, the content area becomes scrollable. The header and footer remain fixed (sticky) within the dialog panel while the body scrolls.
- Body scroll lock: Apply
overflow: hiddento<body>when the dialog is open to prevent the page from scrolling behind the overlay. - Stacking: Dialogs do not stack on top of each other. If a secondary dialog is needed (e.g., a confirmation inside a dialog), use an Alert Dialog (a simpler overlay) positioned at a higher z-index, or reconsider the UX flow.
Accessibility
- The dialog panel MUST have
role="dialog". For pure informational or alert dialogs that require acknowledgment before the user can proceed, userole="alertdialog"instead, which causes screen readers to announce the content immediately. - The dialog MUST have an accessible name. Associate the DialogTitle with the dialog panel via
aria-labelledby. If the dialog also has a description, associate it viaaria-describedbypointing to the DialogDescription element. - The overlay element is purely visual — it should have
aria-hidden="true"or be excluded from the accessibility tree. - When the dialog opens, announce the title to screen readers. Radix UI primitives handle this correctly by moving focus to the dialog and triggering the dialog's accessible name announcement.
- The close button MUST have an
aria-label="Close"(since it is icon-only). aria-modal="true"MUST be set on the dialog panel. This tells screen readers that content outside the dialog is inert.- All content behind the overlay MUST be inert while the dialog is open. Use the
inertattribute on the main page content, or use a library that manages this automatically (e.g., Radix UI Dialog handles this natively). - For wizard dialogs, each step should update the dialog title so screen readers announce the new step when focus moves.
Composition
- A Dialog is triggered by a Button. The trigger button should be labeled in a way that describes what the dialog will do (e.g., "Delete dataset" not just "Delete").
- A Dialog's footer most commonly contains:
- A
secondary / defaultButton labeled "Cancel" on the left. - A
primary / confirmorprimary / dangerButton for the primary action on the right.
- A
- In a Wizard Dialog (multi-step), the footer contains "Back" (secondary) and "Next" / "Create" (primary) buttons. A step indicator (progress dots or a sidebar step list) appears within the dialog content.
- Dialog can contain any form control: Input, Select, Checkbox, Radio Group, Textarea.
- Do NOT open a Dialog from within another Dialog. If you need a confirmation inside a dialog (e.g., "Discard changes?"), use a smaller, purpose-built Alert Dialog or inline warning callout.
- Do NOT use a Dialog to show read-only details — use a Drawer or a Detail Page route.
Guidance
Choosing the right modal size:
- Match the dialog width to its content — overly wide dialogs feel empty; overly narrow dialogs force awkward line wrapping.
- Start with
smfor confirmations,mdfor one- or two-field forms,lgfor multi-field forms, and increase only when the content genuinely requires it.
Writing dialog titles and messages:
- Title: use an action phrase that tells the user what they are deciding or doing. "Delete dataset 'prod-clone'?" is better than "Confirm deletion."
- Body for destructive confirmations: state clearly what will be deleted, that the action cannot be undone, and any downstream consequences. Be specific.
- Buttons: the primary button should echo the title verb. "Delete" not "OK." "Save changes" not "Yes."
Common mistakes to avoid:
- Do NOT use a Dialog for informational content the user does not need to act on. Use a Toast or inline alert.
- Do NOT put a navigation menu or tab structure inside a Dialog — dialogs are task-focused, not exploratory.
- Do NOT dismiss the dialog automatically after an action completes if the action produced an error. Keep the dialog open and show the error state inline.
- Do NOT use a Dialog that cannot be closed. Every dialog — except a hard-blocker like a session expiry screen — must have an escape mechanism (close button or Escape key).
Token Usage
Overlay
- Background:
--force-color-bg-overlay(rgba(0,0,0,0.5)) - Z-index:
--force-z-index-modal-backdrop(400)
Dialog panel
- Background:
--force-color-bg-surface - Border:
1px solid --force-color-border-default(provides definition on light backgrounds) - Border radius:
--force-radius-modal(12px) - Shadow:
--force-shadow-lg - Z-index:
--force-z-index-modal(500) - Padding:
--force-spacing-6(24px) on all sides
Dialog Title
- Color:
--force-color-text-primary - Font size:
--force-font-size-lg(18px) - Font weight:
--force-font-weight-semibold(600) - Line height:
--force-font-line-height-tight(1.25)
Dialog Description
- Color:
--force-color-text-tertiary - Font size:
--force-font-size-sm(14px) - Font weight:
--force-font-weight-regular(400)
Close button
- Same token usage as
transparent / defaultButton - Position: absolute, top
--force-spacing-4(16px), right--force-spacing-4(16px)
Footer
- Gap between buttons:
--force-spacing-2(8px) - Padding-top (separator from content):
--force-spacing-4(16px)
Animation
- Duration:
--force-duration-normal(250ms) - Enter easing:
--force-easing-decelerate - Exit easing:
--force-easing-accelerate