Skip to content
CraftDocs
GitHub
Home
Home
Changelog
What's New
Guide
Guide
Getting Started
Principles
Styling Components
Theme System
Foundations
All Tokens
Color
Elevation
Icons
Motion
Shape
Spacing
Typography
Libraries
Libraries
@xds/cli
@xds/core
Themes
Themes
Theme: daily
Default Theme
Theme: matcha
Neutral Theme
Components
Components
AppShell
AspectRatio
Avatar
Avatar
AvatarStatusDot
Badge
Banner
Breadcrumbs
BreadcrumbItem
Breadcrumbs
Button
Button
IconButton
ToggleButton
ToggleButtonGroup
Calendar
Card
Carousel
Chat
ChatComposer
ChatComposerDrawer
ChatComposerInput
ChatComposerTokenElement
ChatDictationButton
ChatLayout
ChatLayoutScrollButton
ChatMessage
ChatMessageBubble
ChatMessageList
ChatMessageMetadata
ChatSendButton
ChatSystemMessage
ChatTokenizedText
ChatToolCalls
Checkbox
CheckboxInput
CheckboxList
CheckboxListItem
ClickableCard
Code
CodeBlock
Collapsible
Collapsible
CollapsibleGroup
useXDSCollapsible
CommandPalette
CommandPalette
CommandPaletteEmpty
CommandPaletteFooter
CommandPaletteGroup
CommandPaletteInput
CommandPaletteItem
CommandPaletteList
DateInput
Dialog
AlertDialog
Dialog
DialogHeader
useXDSImperativeAlertDialog
useXDSImperativeDialog
Divider
DropdownMenu
DropdownMenu
DropdownMenuDivider
DropdownMenuItem
DropdownMenuItemData
DropdownMenuSection
EmptyState
Field
Field
FieldLabel
FieldStatus
Heading
HoverCard
Icon
Kbd
Layout
Center
FormLayout
Grid
GridSpan
HStack
Layout
LayoutContainer
LayoutContent
LayoutFooter
LayoutHeader
LayoutPanel
Section
StackItem
VStack
Link
List
List
ListItem
Markdown
MetadataList
MetadataList
MetadataListItem
MobileNav
MoreMenu
NavIcon
NavMenuItem
NumberInput
OverflowList
Pagination
Popover
PowerSearch
ProgressBar
Radio
RadioList
RadioListItem
Resizable
ResizeHandle
useXDSResizable
SegmentedControl
SegmentedControl
SegmentedControlItem
SelectableCard
Selector
MultiSelector
Selector
SelectorOption
SideNav
SideNav
SideNavCollapseButton
SideNavHeading
SideNavItem
SideNavSection
Skeleton
Slider
Spinner
StatusDot
Switch
Table
BaseTable
Table
TableCell
TableHeaderCell
TableRow
useXDSTableColumnSettings
useXDSTablePagination
useXDSTableSelection
useXDSTableSelectionState
useXDSTableSortable
Tabs
Tab
TabList
TabMenu
Text
TextArea
TextInput
Thumbnail
TimeInput
Timestamp
Toast
Toast
useXDSToast
Token
Tokenizer
Toolbar
Tooltip
TopNav
TopNav
TopNavHeading
TopNavItem
TopNavMegaMenu
TopNavMegaMenuFeaturedCard
TopNavMegaMenuItem
TopNavMenu
TreeList
Typeahead
BaseTypeahead
Typeahead
TypeaheadItem
useXDSHoverCard
useXDSPopover
useXDSTooltip
Utilities
Utilities
LinkProvider
MediaTheme
SyntaxTheme
Theme
useClickableContainer
useEntryAnimation
useFocusTrap
useGridFocus
useImageMode
useInputContainer
useListFocus
useMediaQuery
useOverflow
useScrollLock
useScrollOverflow
useXDSLayer
useXDSStreamingText
Terms of UsePrivacy Policy
Type to search
↑↓Navigate↵SelectEscClose
Dialog@xds/core · XDSDialog v0.0.13

Usage

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 `useXDSImperativeDialog` hook — call `dialog.show(content)` and render `dialog.element` in your tree.
ts
import {XDSDialog} from '@xds/core/Dialog'

Best practices

GuidancePractices
DoChoose the right purpose: info for dismissable content, form to prevent accidental backdrop dismissal, required when the user must respond.
DoInclude a clear title in the header so users immediately understand what the dialog is asking.
DoUse purpose="form" for dialogs with inputs so the user can't accidentally lose data by clicking the backdrop.
DoKeep dialogs focused on a single task — if the content grows beyond what fits, consider a full page instead.
Don'tUse a dialog for simple messages that could be shown inline or as a toast notification.
Don'tNest dialogs inside other dialogs — restructure the flow into steps within a single dialog instead.
Don'tUse the fullscreen variant for simple confirmations — it is meant for complex content like editors or long forms.

Sub-components

Dialog is a compound component with 3 sub-components.

XDSDialog

Modal dialog using the native <dialog> element.
PropTypeDescription
isOpenrequired
booleanWhether the dialog is open (required).
onOpenChangerequired
(isOpen: boolean) => unknownCallback when dialog visibility changes (required).
childrenrequired
ReactNodeDialog content.
width
number | string (default: 400)Width of the dialog in pixels or any CSS value.
maxHeight
number | string (default: '75vh')Maximum height of the dialog.
position
XDSDialogPositionStatic position for the dialog; centered by default when omitted.
variant
'standard' | 'fullscreen' (default: 'standard')Dialog variant — fullscreen expands to fill the entire viewport.
purpose
'required' | 'form' | 'info' (default: 'info')Controls dismissal behavior: required disables Escape and backdrop click; form disables backdrop click after interaction; info allows both.
isInline
boolean (default: false)Renders dialog content inline without the <dialog> element, backdrop, or modal behavior. For documentation previews and showcases only.

XDSDialogHeader

Header for dialogs with a title, optional subtitle, close button, and start/end content slots.
PropTypeDescription
title
stringDialog title (receives focus on open).
subtitle
stringSubtitle below the title.
onOpenChange
(isOpen: boolean) => unknownClose button callback (no button if omitted).
startContent
ReactNodeContent before the title (e.g., a back button).
endContent
ReactNodeContent after the title, before close button.
hasDivider
boolean (default: true)Adds border at the bottom edge.

useXDSImperativeDialog

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.
PropTypeDescription
show
(content: ReactNode, options?: DialogOptions) => voidShow the dialog with the given content. Options are the same as XDSDialog props minus isOpen/onOpenChange/children.
hide
() => voidHide the dialog.
isOpen
booleanWhether the dialog is currently open.
element
ReactNodeThe dialog element — render this in your JSX tree.

Examples

Common configurations, variations, and states.
Dialog — ConfirmationAsks the user to confirm a destructive action before it happens. Use before deleting projects, removing team members, revoking API keys, or any irreversible operation.
tsx
'use client';
​
import {
XDSDialog,
XDSDialogHeader,
useXDSImperativeDialog,
} from '@xds/core/Dialog';
import {
XDSLayout,
XDSLayoutContent,
XDSLayoutFooter,
XDSHStack,
} from '@xds/core/Layout';
import {XDSButton} from '@xds/core/Button';
import {XDSText} from '@xds/core/Text';
​
function Content({onClose}: {onClose: () => void}) {
return (
<XDSLayout
header={
<XDSDialogHeader
title="Delete project?"
onOpenChange={() => onClose()}
/>
}
content={
<XDSLayoutContent>
<XDSText type="body">
This will permanently delete &quot;Marketing Dashboard&quot; and all
of its data. This action cannot be undone.
</XDSText>
</XDSLayoutContent>
}
footer={
<XDSLayoutFooter>
<XDSHStack gap={2} hAlign="end">
<XDSButton label="Cancel" variant="secondary" onClick={onClose} />
<XDSButton label="Delete" variant="destructive" onClick={onClose} />
</XDSHStack>
</XDSLayoutFooter>
}
/>
);
}
​
// Remove isInline for production — dialogs should be modal.
export default function DialogConfirmationDialog() {
const dialog = useXDSImperativeDialog({width: 400, purpose: 'form'});
​
return (
<>
<XDSDialog
isOpen
isInline
onOpenChange={() => {}}
width={400}
purpose="form">
<Content
onClose={() => dialog.show(<Content onClose={() => dialog.hide()} />)}
/>
</XDSDialog>
{dialog.element}
</>
);
}
Dialog — FormCollects user input without navigating away from the page. Uses purpose=
tsx
'use client';
​
import {useState} from 'react';
import {
XDSDialog,
XDSDialogHeader,
useXDSImperativeDialog,
} from '@xds/core/Dialog';
import {
XDSLayout,
XDSLayoutContent,
XDSLayoutFooter,
XDSHStack,
XDSVStack,
} from '@xds/core/Layout';
import {XDSButton} from '@xds/core/Button';
import {XDSTextInput} from '@xds/core/TextInput';
import {XDSTextArea} from '@xds/core/TextArea';
​
function Content({onClose}: {onClose: () => void}) {
const [name, setName] = useState('Ruby Cheung');
const [bio, setBio] = useState('Design systems engineer');
​
return (
<XDSLayout
header={
<XDSDialogHeader
title="Edit profile"
subtitle="Update your display name and bio"
onOpenChange={() => onClose()}
/>
}
content={
<XDSLayoutContent>
<XDSVStack gap={4}>
<XDSTextInput
label="Display name"
value={name}
onChange={setName}
placeholder="Enter your name"
/>
<XDSTextArea
label="Bio"
value={bio}
onChange={setBio}
placeholder="Tell us about yourself"
/>
</XDSVStack>
</XDSLayoutContent>
}
footer={
<XDSLayoutFooter>
<XDSHStack gap={2} hAlign="end">
<XDSButton label="Cancel" variant="secondary" onClick={onClose} />
<XDSButton label="Save" variant="primary" onClick={onClose} />
</XDSHStack>
</XDSLayoutFooter>
}
/>
);
}
​
// Remove isInline for production — dialogs should be modal.
export default function DialogFormDialog() {
const dialog = useXDSImperativeDialog({purpose: 'form', width: 480});
​
return (
<>
<XDSDialog
isOpen
isInline
onOpenChange={() => {}}
purpose="form"
width={480}>
<Content
onClose={() => dialog.show(<Content onClose={() => dialog.hide()} />)}
/>
</XDSDialog>
{dialog.element}
</>
);
}
Dialog — Fullscreen
tsx
'use client';
​
import {useState} from 'react';
import {XDSDialog, XDSDialogHeader} from '@xds/core/Dialog';
import {
XDSLayout,
XDSLayoutContent,
XDSLayoutFooter,
XDSHStack,
XDSVStack,
} from '@xds/core/Layout';
import {XDSButton} from '@xds/core/Button';
import {XDSText} from '@xds/core/Text';
import {XDSCard} from '@xds/core/Card';
​
const SECTIONS = [
{
title: 'Getting started',
body: 'Create your first project by clicking New Project in the sidebar. Choose a template or start from scratch.',
},
{
title: 'Team members',
body: 'Invite collaborators from Settings > Team. Each member can have Admin, Editor, or Viewer permissions.',
},
{
title: 'Billing',
body: 'Free plans include up to 3 projects. Upgrade to Pro for unlimited projects and priority support.',
},
{
title: 'API access',
body: 'Generate API keys from Settings > Developer. Rate limits are 1,000 requests per minute on free plans.',
},
{
title: 'Data export',
body: 'Export your data anytime from Settings > Data. Exports are available as CSV or JSON within 24 hours.',
},
];
​
export default function DialogFullscreenDialog() {
const [isOpen, setIsOpen] = useState(false);
​
return (
<XDSCard>
<XDSVStack gap={3}>
<XDSVStack gap={1}>
<XDSText type="body" weight="bold">
Help &amp; Documentation
</XDSText>
<XDSText type="supporting" color="secondary">
5 articles · Last updated Apr 2026
</XDSText>
</XDSVStack>
<XDSButton
label="Open documentation"
variant="secondary"
onClick={() => setIsOpen(true)}
/>
</XDSVStack>
<XDSDialog isOpen={isOpen} onOpenChange={setIsOpen} variant="fullscreen">
<XDSLayout
header={
<XDSDialogHeader
title="Documentation"
subtitle="Everything you need to get started"
onOpenChange={setIsOpen}
/>
}
content={
<XDSLayoutContent>
<XDSVStack gap={4}>
{SECTIONS.map(({title, body}) => (
<XDSVStack key={title} gap={1}>
<XDSText type="body" weight="bold">
{title}
</XDSText>
<XDSText type="body">{body}</XDSText>
</XDSVStack>
))}
</XDSVStack>
</XDSLayoutContent>
}
footer={
<XDSLayoutFooter>
<XDSHStack hAlign="end">
<XDSButton
label="Close"
variant="primary"
onClick={() => setIsOpen(false)}
/>
</XDSHStack>
</XDSLayoutFooter>
}
/>
</XDSDialog>
</XDSCard>
);
}
Dialog — RequiredCannot be dismissed by Escape or backdrop click — the user must explicitly choose an action. Uses purpose=
tsx
'use client';
​
import {
XDSDialog,
XDSDialogHeader,
useXDSImperativeDialog,
} from '@xds/core/Dialog';
import {
XDSLayout,
XDSLayoutContent,
XDSLayoutFooter,
XDSHStack,
} from '@xds/core/Layout';
import {XDSButton} from '@xds/core/Button';
import {XDSText} from '@xds/core/Text';
​
function Content({onClose}: {onClose: () => void}) {
return (
<XDSLayout
header={
<XDSDialogHeader
title="Transfer project ownership"
subtitle="This action requires confirmation from the new owner"
/>
}
content={
<XDSLayoutContent>
<XDSText type="body">
You are about to transfer &quot;Marketing Dashboard&quot; to Sarah
Chen. Once accepted, you will lose admin access.
</XDSText>
</XDSLayoutContent>
}
footer={
<XDSLayoutFooter>
<XDSHStack gap={2} hAlign="end">
<XDSButton label="Cancel" variant="secondary" onClick={onClose} />
<XDSButton label="Transfer" variant="primary" onClick={onClose} />
</XDSHStack>
</XDSLayoutFooter>
}
/>
);
}
​
// Remove isInline for production — dialogs should be modal.
export default function DialogWithSubtitle() {
const dialog = useXDSImperativeDialog({purpose: 'required'});
​
return (
<>
<XDSDialog isOpen isInline onOpenChange={() => {}} purpose="required">
<Content
onClose={() => dialog.show(<Content onClose={() => dialog.hide()} />)}
/>
</XDSDialog>
{dialog.element}
</>
);
}
Dialog — Scrollable
tsx
'use client';
​
import {
XDSDialog,
XDSDialogHeader,
useXDSImperativeDialog,
} from '@xds/core/Dialog';
import {
XDSLayout,
XDSLayoutContent,
XDSLayoutFooter,
XDSHStack,
XDSVStack,
} from '@xds/core/Layout';
import {XDSButton} from '@xds/core/Button';
import {XDSText} from '@xds/core/Text';
​
const TERMS = [
'You agree to use the service only for lawful purposes and in compliance with all applicable laws and regulations in your jurisdiction.',
'Your account credentials are your responsibility. Notify us immediately if you suspect unauthorized access to your account.',
'We reserve the right to suspend accounts that violate these terms or engage in abusive behavior toward other users.',
'Content you upload remains your property. You grant us a license to host and display it within the service.',
'We may update these terms at any time. Continued use after changes constitutes acceptance of the updated terms.',
'The service is provided as-is without warranties of any kind. We are not liable for data loss or service interruptions.',
'You may cancel your account at any time. Your data will be deleted within 30 days of cancellation.',
'Disputes will be resolved through binding arbitration in accordance with applicable regulations.',
'You agree not to reverse-engineer, decompile, or disassemble any part of the service or its underlying technology.',
'We may collect anonymized usage data to improve the service. Personal data is handled per our Privacy Policy.',
'Third-party integrations are governed by their own terms. We are not responsible for third-party service outages.',
'You are responsible for maintaining backups of your data. We provide export tools but do not guarantee data recovery.',
'Commercial use requires a Business plan. Free accounts are limited to personal and non-commercial projects.',
'We may introduce new features or discontinue existing ones with 30 days notice via email or in-app notification.',
'Violation of these terms may result in immediate termination of your account without prior notice or refund.',
];
​
function Content({onClose}: {onClose: () => void}) {
return (
<XDSLayout
header={
<XDSDialogHeader
title="Terms and Conditions"
onOpenChange={() => onClose()}
/>
}
content={
<XDSLayoutContent>
<XDSVStack gap={3}>
{TERMS.map((term, i) => (
<XDSText type="body" key={i}>
{i + 1}. {term}
</XDSText>
))}
</XDSVStack>
</XDSLayoutContent>
}
footer={
<XDSLayoutFooter>
<XDSHStack gap={2} hAlign="end">
<XDSButton label="Decline" variant="secondary" onClick={onClose} />
<XDSButton label="Accept" variant="primary" onClick={onClose} />
</XDSHStack>
</XDSLayoutFooter>
}
/>
);
}
​
// Remove isInline for production — dialogs should be modal.
export default function DialogScrollingContent() {
const dialog = useXDSImperativeDialog({maxHeight: '50vh'});
​
return (
<>
<XDSDialog isOpen isInline onOpenChange={() => {}} maxHeight={360}>
<Content
onClose={() => dialog.show(<Content onClose={() => dialog.hide()} />)}
/>
</XDSDialog>
{dialog.element}
</>
);
}

Showcase source

tsx
'use client';
​
import {XDSDialog, XDSDialogHeader} from '@xds/core/Dialog';
import {XDSLayout, XDSLayoutContent} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
​
// Remove isInline for production — dialogs should be modal.
export default function DialogShowcase() {
return (
<XDSDialog isOpen isInline onOpenChange={() => {}}>
<XDSLayout
header={<XDSDialogHeader title="Modal Title" onOpenChange={() => {}} />}
content={
<XDSLayoutContent>
<XDSText type="body">Dialog content goes here.</XDSText>
</XDSLayoutContent>
}
/>
</XDSDialog>
);
}