Dialog
Dialog displays a modal overlay that blocks interaction with the page until the user responds. Use it for delete confirmations, edit forms, terms acceptance, or any decision that should not be skipped.
For cases where you want to show a dialog without managing open state, use the useImperativeDialog hook: call dialog.show(content) and render dialog.element in your tree.
Installation
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
Requires React 19+, base CSS and a Theme provider. See Setup.
Dialog
Dialog displays a modal overlay that blocks interaction with the page until the user responds. Use it for delete confirmations, edit forms, terms acceptance, or any decision that should not be skipped.
For cases where you want to show a dialog without managing open state, use the useImperativeDialog hook: call dialog.show(content) and render dialog.element in your tree.
| Prop | Type | Default | Description |
|---|---|---|---|
| isOpen * | boolean | Whether the dialog is open. | |
| onOpenChange * | (isOpen: boolean) => unknown | Callback when dialog visibility changes. | |
| children * | ReactNode | Dialog content. | |
| width | number | string | 400 | Preferred width of the dialog in pixels or any CSS value. Standard dialogs clamp to their container and the dynamic viewport with spacing-token gutters so narrow viewports keep content on screen. |
| maxHeight | number | string | '75dvh' | Maximum height of the dialog. Defaults to a dynamic viewport value so browser UI changes are reflected where supported. |
| position | DialogPosition | Static position for the dialog; centered by default when omitted. Use logical start/end for inline offsets so positioned dialogs mirror correctly under RTL. | |
| variant | 'standard' | 'fullscreen' | 'standard' | Dialog variant: fullscreen expands to fill the entire viewport. |
| purpose | 'required' | 'form' | 'info' | 'info' | Controls dismissal behavior: required disables Escape and backdrop click; form disables backdrop click after interaction; info allows both. |
| padding | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | Internal padding of the dialog using the spacing scale step. | |
| isInline | boolean | false | Renders dialog content inline without the <dialog> element, backdrop, or modal behavior. For documentation previews and showcases only. |
Usage guidance
- Choose the right purpose: info for dismissable content, form to prevent accidental backdrop dismissal, required when the user must respond.
- Include a clear title in the header so users immediately understand what the dialog is asking.
- Use purpose="form" for dialogs with inputs so the user can't accidentally lose data by clicking the backdrop.
- Keep dialogs focused on a single task; if the content grows beyond what fits, consider a full page instead.
- Avoid: Use a dialog for simple messages that could be shown inline or as a toast notification.
- Avoid: Nest dialogs inside other dialogs; restructure the flow into steps within a single dialog instead.
- Avoid: Use the fullscreen variant for simple confirmations; it is meant for complex content like editors or long forms.
Theming API
{
"container": true,
"targets": [
{
"className": "astryx-dialog",
"visualProps": [
"variant"
]
},
{
"className": "astryx-dialog-header"
},
{
"className": "astryx-dialog-header-start-content"
},
{
"className": "astryx-dialog-header-title-block"
},
{
"className": "astryx-dialog-header-end-content"
},
{
"className": "astryx-dialog-header-close-icon"
}
],
"vars": [
{
"name": "--_dialog-radius",
"description": "Border radius of the dialog",
"default": "var(--radius-container)",
"private": true
}
],
"derived": [
{
"property": "borderRadius",
"vars": [
"--_dialog-radius"
]
},
{
"property": "padding",
"expand": "container"
}
]
}
DialogHeader
useImperativeDialog
Anatomy
- Header: Title, optional subtitle, and close button. The title receives focus on open and labels the dialog via aria-labelledby.
- Body: The main content area: text, forms, lists, or any layout.
- Footer: Action buttons like Save/Cancel or Accept/Decline, aligned to the end.
- Backdrop: Semi-transparent overlay behind the dialog that blocks page interaction.
Dialog Header
Use DialogHeader to give a dialog a labelled title area and optional close control.
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | Dialog title, rendered inside the focusable h2 (receives focus on open; its text content labels the dialog via aria-labelledby unless the Dialog has an explicit aria-label/aria-labelledby). Keep it inline, non-interactive, and non-empty. | |
| subtitle | ReactNode | Subtitle below the title. Accepts inline content such as a Link; avoid block elements. | |
| onOpenChange | (isOpen: boolean) => unknown | Close button callback (no button if omitted). | |
| startContent | ReactNode | Content before the title (e.g., a back button). | |
| endContent | ReactNode | Content after the title, before close button. | |
| endContentEdgeCompensation | 'inline' | 'block' | 'all' | Selects compensation axes for the end-content slot. Omit to preserve automatic close-action compensation. | |
| hasDivider | boolean | true | Adds border at the bottom edge. |
Basic
import {DialogHeader} from '@astryxdesign/core/Dialog';
<DialogHeader title="Delete file?" subtitle="This action cannot be undone." />;
With close button
import {useState} from 'react';
import {DialogHeader} from '@astryxdesign/core/Dialog';
function Header() {
const [, setIsOpen] = useState(true);
// Passing onOpenChange renders a close button that calls it with false.
return <DialogHeader title="Settings" onOpenChange={setIsOpen} />;
}
With a link in the subtitle
import {DialogHeader} from '@astryxdesign/core/Dialog';
import {Link} from '@astryxdesign/core/Link';
<DialogHeader
title="Share conversation"
subtitle={
<>
Anyone with the link can view it. Review the{' '}
<Link href="#sharing-policy">sharing policy</Link> first.
</>
}
/>;
With start and end content
import {DialogHeader} from '@astryxdesign/core/Dialog';
import {Icon} from '@astryxdesign/core/Icon';
import {Badge} from '@astryxdesign/core/Badge';
<DialogHeader
title="Notifications"
startContent={<Icon icon="chevronLeft" size="sm" />}
endContent={<Badge label="3" />}
/>;
Theming API
{
"targets": [
{
"className": "astryx-dialog-header"
},
{
"className": "astryx-dialog-header-start-content"
},
{
"className": "astryx-dialog-header-title-block"
},
{
"className": "astryx-dialog-header-end-content"
},
{
"className": "astryx-dialog-header-close-icon"
}
]
}
Anatomy
- Header row: Arranges the title block, optional start/end content, and close control.
- Start content: Wraps optional leading content.
- Title block: Groups the title and optional subtitle.
- End content: Groups optional trailing content with the optional close control.
- Close icon: Visual close glyph inside the close button.
useImperativeDialog
Hook for showing a dialog without managing open state. Call dialog.show(content, options) to open and dialog.hide() to close. Render dialog.element in your JSX tree.
| Prop | Type | Default | Description |
|---|---|---|---|
| show | (content: ReactNode, options?: DialogOptions) => void | Show the dialog with the given content. Options are the same as Dialog props minus isOpen/onOpenChange/children. | |
| hide | () => void | Hide the dialog. | |
| isOpen | boolean | Whether the dialog is currently open. | |
| element | ReactNode | The dialog element: render this in your JSX tree. |
Examples
- Dialog Header: DialogHeader provides a structured header for dialogs with slots for title, subtitle, close button, and optional start or end content.
- Dialog: Modal dialog with a header, body content, and close button.
- Dialog — Adaptive presentation: Opt-in recipe for an AdaptiveDialog wrapper: Dialog remains the default everywhere, while touchPresentation="bottom-sheet" switches only at lg and below when pointer is coarse and hover is unavailable. Includes a deterministic presentation override for tests/unusual environments and notes that BottomSheet purpose controls swipe and scrim dismissal. Usage examples: touchPresentation="dialog" keeps Dialog, "fullscreen" chooses fullscreen Dialog, and "bottom-sheet" chooses BottomSheet only for the touch-oriented range. Keep presentation as the deterministic override. Do not use this by default for AlertDialog or destructive confirmations.
- Dialog — Confirmation: Asks the user to confirm a destructive action before it happens. Use before deleting projects, removing team members, revoking API keys, or any irreversible operation.
- Dialog — Form: Collects user input without navigating away from the page. Uses purpose="form" so clicking the backdrop won't close it. Use for editing profiles, creating items, or updating settings inline.
- Dialog — Fullscreen: Takes over the entire viewport for content that needs maximum space. Use for documentation viewers, rich text editors, multi-step wizards, or media previews where the standard dialog width is too narrow.
- DialogHeader — Basic: A DialogHeader with a title, subtitle, and close button, placed in the header slot of a Dialog Layout. Pass onOpenChange to render the close button.
- Dialog — Scrollable: Constrains the dialog height and scrolls the body when content overflows. Use for terms and conditions, license agreements, changelogs, or any long-form content the user needs to review before accepting.
- Dialog — Required: Cannot be dismissed by Escape or backdrop click; the user must explicitly choose an action. Uses purpose="required". Use for ownership transfers, legal acknowledgements, or critical decisions where skipping is not an option.