Dialog
Material Design 3 dialog component using native HTML dialog element for accessible modal interactions
Modal / Dialog
Modal and Dialog use only the native HTML <dialog> element. Dialog styles <dialog class="dialog"> and relies on showModal() semantics for background inertness, focus containment, Escape dismissal, and focus restoration. The Modal component uses the same native behavior with .modal and .modal-box presentation classes. Custom div overlays, visibility classes, and custom focus traps are not supported.
Why Native Dialog?
Opening native <dialog> with showModal() provides:
- Modal focus behavior - The browser moves focus into the dialog, contains sequential focus, and restores focus when appropriate
- Native backdrop - Via
::backdroppseudo-element - Background inertness - Content outside the modal is not interactive
- Escape dismissal - The browser handles the dialog cancel action
Use the Invoker Commands API (command + commandfor) where supported, or call showModal() and close() from application code. Setting the open attribute, changing CSS visibility, or adding aria-modal does not create equivalent modal behavior.
Basic Usage
A basic dialog consists of a <dialog> element with inner structure for header, body, and footer. Open it with command="show-modal" and close it with command="close", both pointing at the dialog id via commandfor. Prefix demo IDs with demo- so they do not collide with heading IDs.
Basic Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-basic">
Open Dialog
</button>
<dialog class="dialog" id="demo-dialog-basic">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Dialog Title</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-basic" aria-label="Close dialog">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="dialog-body">
<p>This is the dialog content. You can add any content here.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-basic">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-basic" autofocus>Confirm</button>
</div>
</div>
</dialog>Size Variants
Small Dialog
Perfect for simple confirmations and alerts.
Small Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-sm">
Open Small Dialog
</button>
<dialog class="dialog dialog-sm" id="demo-dialog-sm">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Small Dialog</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-sm" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>This is a small dialog (max-width: 20rem).</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-sm">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-sm">OK</button>
</div>
</div>
</dialog>Large Dialog
For dialogs with more content.
Large Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-lg">
Open Large Dialog
</button>
<dialog class="dialog dialog-lg" id="demo-dialog-lg">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Large Dialog</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-lg" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>This is a large dialog (max-width: 40rem). It provides more space for complex content like forms, tables, or detailed information.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-lg">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-lg">Continue</button>
</div>
</div>
</dialog>Extra Large Dialog
For complex forms or detailed content.
Extra Large Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-xl">
Open Extra Large Dialog
</button>
<dialog class="dialog dialog-xl" id="demo-dialog-xl">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Extra Large Dialog</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-xl" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>This is an extra large dialog (max-width: 56rem). Use it for complex workflows, data tables, or multi-step forms.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-xl">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-xl">Submit</button>
</div>
</div>
</dialog>Fullscreen Dialog
Covers the entire viewport, ideal for mobile experiences or immersive content.
Fullscreen Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-fullscreen">
Open Fullscreen Dialog
</button>
<dialog class="dialog dialog-fullscreen" id="demo-dialog-fullscreen">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Fullscreen Dialog</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-fullscreen" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>This dialog covers the entire screen with no border radius. Perfect for mobile experiences or content that needs maximum space.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-fullscreen">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-fullscreen">Done</button>
</div>
</div>
</dialog>Dialog Variants
Alert Dialog
Simplified dialog for confirmations and critical actions.
Alert Dialog
<button class="btn btn-error" command="show-modal" commandfor="demo-dialog-alert">
Delete Item
</button>
<dialog class="dialog dialog-sm" id="demo-dialog-alert">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Delete Item?</h2>
</div>
<div class="dialog-body">
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-alert">Cancel</button>
<button class="btn btn-error" command="close" commandfor="demo-dialog-alert">Delete</button>
</div>
</div>
</dialog>Dialog with Dividers
Add visual separation between sections using the dialog-divider class.
Dialog with Dividers
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-divider">
Open Dialog with Dividers
</button>
<dialog class="dialog dialog-divider" id="demo-dialog-divider">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Settings</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-divider" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>The header and footer have visible divider lines for clear section separation.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-divider">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-divider">Save</button>
</div>
</div>
</dialog>Footer Alignment
Right-Aligned (Default)
Right-Aligned Footer
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-footer-end">
Open Dialog
</button>
<dialog class="dialog" id="demo-dialog-footer-end">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Right-Aligned Actions</h2>
</div>
<div class="dialog-body">
<p>Footer actions are right-aligned by default.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-footer-end">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-footer-end">Confirm</button>
</div>
</div>
</dialog>Left-Aligned Footer
Left-Aligned Footer
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-footer-start">
Open Dialog
</button>
<dialog class="dialog" id="demo-dialog-footer-start">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Left-Aligned Actions</h2>
</div>
<div class="dialog-body">
<p>Use dialog-footer-start for left-aligned actions.</p>
</div>
<div class="dialog-footer dialog-footer-start">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-footer-start">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-footer-start">Confirm</button>
</div>
</div>
</dialog>Center-Aligned Footer
Center-Aligned Footer
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-footer-center">
Open Dialog
</button>
<dialog class="dialog" id="demo-dialog-footer-center">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Center-Aligned Actions</h2>
</div>
<div class="dialog-body">
<p>Use dialog-footer-center for centered actions.</p>
</div>
<div class="dialog-footer dialog-footer-center">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-footer-center">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-footer-center">Confirm</button>
</div>
</div>
</dialog>Space-Between Footer
Space-Between Footer
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-footer-between">
Open Dialog
</button>
<dialog class="dialog" id="demo-dialog-footer-between">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Space-Between Actions</h2>
</div>
<div class="dialog-body">
<p>Use dialog-footer-between to space actions apart.</p>
</div>
<div class="dialog-footer dialog-footer-between">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-footer-between">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-footer-between">Confirm</button>
</div>
</div>
</dialog>Scrollable Dialog
For dialogs with long content, add the dialog-scrollable class:
Scrollable Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-scrollable">
Open Scrollable Dialog
</button>
<dialog class="dialog dialog-scrollable" id="demo-dialog-scrollable">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Terms and Conditions</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-scrollable" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis.</p>
<p>Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
<p>Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-scrollable">Decline</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-scrollable">Accept</button>
</div>
</div>
</dialog>Position Variants
Top Position
Top Positioned Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-top">
Open Top Dialog
</button>
<dialog class="dialog dialog-top" id="demo-dialog-top">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Top Dialog</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-top" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>This dialog appears at the top of the viewport.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-primary" command="close" commandfor="demo-dialog-top">OK</button>
</div>
</div>
</dialog>Bottom Position
Bottom Positioned Dialog
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-bottom">
Open Bottom Dialog
</button>
<dialog class="dialog dialog-bottom" id="demo-dialog-bottom">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Bottom Dialog</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-bottom" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<p>This dialog appears at the bottom of the viewport.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-primary" command="close" commandfor="demo-dialog-bottom">OK</button>
</div>
</div>
</dialog>JavaScript Integration
Open and close buttons may use the Invoker Commands API (command / commandfor). Use showModal() as the application fallback and for programmatic control. Do not substitute dialog.open = true or setAttribute('open', ''): those open a non-modal surface without modal focus or inertness.
// Get dialog reference
const dialog = document.querySelector('dialog');
// Open as a real modal (with backdrop and background inertness)
dialog.showModal();
// Open as non-modal (no backdrop)
dialog.show();
// Close dialog — prefer command="close" on buttons
dialog.close();
// Check if open
if (dialog.open) {
console.log('Dialog is open');
}
// Listen for close event
dialog.addEventListener('close', () => {
console.log('Dialog was closed');
});
// Optional policy: close on backdrop click (not a native default)
dialog.addEventListener('click', (e) => {
if (e.target === dialog) {
dialog.close();
}
});
Backdrop Click Example
Backdrop click dismissal is opt-in. Some destructive or multi-step dialogs should require an explicit action instead; do not attach this handler globally.
Backdrop Click Example
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-js">Open Dialog</button>
<dialog class="dialog" id="demo-dialog-js">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Interactive Dialog</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-js" aria-label="Close dialog">×</button>
</div>
<div class="dialog-body">
<p>Open and close buttons use Command API. JavaScript only handles backdrop click.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-js">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-js">Confirm</button>
</div>
</div>
</dialog>
<script>
const dialog = document.getElementById('demo-dialog-js');
dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.close();
});
</script>Framework Examples
React
React 19 maps the HTML commandfor attribute to the commandFor prop.
interface DialogProps {
title: string;
children: React.ReactNode;
onClose?: () => void;
}
export function Dialog({ title, children, onClose }: DialogProps) {
const dialogId = 'demo-dialog-react';
return (
<>
<button className="btn btn-primary" command="show-modal" commandFor={dialogId}>
Open Dialog
</button>
<dialog
id={dialogId}
className="dialog"
onClose={onClose}
onClick={(e) => e.target === e.currentTarget && e.currentTarget.close()}
>
<div className="dialog-box">
<div className="dialog-header">
<h2 className="dialog-title">{title}</h2>
<button className="dialog-close" command="close" commandFor={dialogId} aria-label="Close">
×
</button>
</div>
<div className="dialog-body">{children}</div>
<div className="dialog-footer">
<button className="btn btn-ghost" command="close" commandFor={dialogId}>Cancel</button>
<button className="btn btn-primary" command="close" commandFor={dialogId}>Confirm</button>
</div>
</div>
</dialog>
</>
);
}
Vue
<template>
<button class="btn btn-primary" command="show-modal" commandfor="demo-dialog-vue">Open Dialog</button>
<dialog id="demo-dialog-vue" class="dialog" @click.self="$event.currentTarget.close()">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">{{ title }}</h2>
<button class="dialog-close" command="close" commandfor="demo-dialog-vue" aria-label="Close">×</button>
</div>
<div class="dialog-body">
<slot />
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" command="close" commandfor="demo-dialog-vue">Cancel</button>
<button class="btn btn-primary" command="close" commandfor="demo-dialog-vue">Confirm</button>
</div>
</div>
</dialog>
</template>
<script setup>
defineProps({ title: String });
</script>
Best Practices
Accessibility
Native modal opening handles much of the interaction model, while authors still own content and initial-focus choices:
- Focus trapping - Focus is trapped within the modal when open
- Escape key - Pressing Escape closes the dialog
- Focus restoration - Focus returns to the trigger element when closed
- Accessible name - Supply
aria-labelledbyoraria-label - Initial focus - Use
autofocuson the most suitable control when the browser’s default first-focusable choice is not appropriate
Add descriptive labels for additional accessibility:
<dialog class="dialog" id="demo-dialog-a11y" aria-labelledby="demo-dialog-a11y-title" aria-describedby="demo-dialog-a11y-desc">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title" id="demo-dialog-a11y-title">Confirm Action</h2>
</div>
<div class="dialog-body">
<p id="demo-dialog-a11y-desc">Are you sure you want to proceed?</p>
</div>
</div>
</dialog>
User Experience
- Use dialogs sparingly - Don’t interrupt the user unnecessarily
- Keep content focused - Present one task or decision at a time
- Provide clear actions - Use descriptive button labels
- Enable easy dismissal - Escape is built in; add a small script if you want backdrop click to close
- Consider mobile - Use fullscreen variant on small screens
Background inertness and scroll locking
showModal() makes the rest of the document inert; that is the modal interaction boundary. Page scroll locking is a separate application policy. Dialog CSS constrains and scrolls long .dialog-body content but does not add a global body overflow class. If your product requires the page itself not to scroll, add and remove that state in the same application lifecycle that calls showModal() and handles close/cancel.
Browser requirements
Native <dialog> and showModal() are the baseline. Invoker Commands are progressive enhancement: when command="show-modal" is unavailable, attach a click handler that calls the target dialog’s showModal(). Never fall back to only setting open for a modal task.
API Reference
Class Names
| Class | Description |
|---|---|
.dialog |
Base dialog styles (apply to <dialog> element) |
.dialog-box |
Inner container for flex layout |
.dialog-header |
Header section with title and close button |
.dialog-title |
Dialog title text |
.dialog-close |
Close button in header |
.dialog-body |
Content section |
.dialog-footer |
Footer with action buttons |
.dialog-sm |
Small dialog (max-width: 20rem) |
.dialog-lg |
Large dialog (max-width: 40rem) |
.dialog-xl |
Extra large dialog (max-width: 56rem) |
.dialog-fullscreen |
Fullscreen dialog |
.dialog-top |
Position at top of viewport |
.dialog-bottom |
Position at bottom of viewport |
.dialog-scrollable |
Enable body scrolling (max-height: 50vh) |
.dialog-divider |
Add dividers to header and footer |
.dialog-footer-start |
Left-align footer actions |
.dialog-footer-center |
Center-align footer actions |
.dialog-footer-between |
Space-between footer actions |
Invoker Commands
Use these attributes on <button> elements. Give each <dialog> a unique id (prefix docs demos with demo-).
| Attribute | Description |
|---|---|
commandfor="id" |
ID of the <dialog> to control |
command="show-modal" |
Open the dialog as a modal |
command="close" |
Close the dialog |
Native Dialog Methods
Use these for programmatic control. Prefer Invoker Commands on buttons.
| Method | Description |
|---|---|
showModal() |
Open dialog as modal with backdrop |
show() |
Open dialog without backdrop |
close() |
Close the dialog |
Native Dialog Properties
| Property | Description |
|---|---|
open |
Boolean indicating if dialog is open |
returnValue |
String value passed to close() |
Related Components
- Button - Dialog action buttons
- Modal - Legacy CSS visibility API retained for compatibility
- Actions overview - Choosing among action primitives
- Card - Similar container component
- Alert - For inline notifications