Dialog
Material Design 3 dialog component using native HTML dialog element for accessible modal interactions
Dialog
Dialogs inform users about a task and can contain critical information, require decisions, or involve multiple tasks. @duskmoon-dev/core provides Material Design 3 dialog styles built on the native HTML <dialog> element for excellent accessibility and built-in functionality.
Why Native Dialog?
The native <dialog> element provides:
- Built-in accessibility - Focus trapping, Escape key handling, and ARIA attributes
- Native backdrop - Via
::backdroppseudo-element - Modal behavior -
showModal()method handles overlay and focus - Browser-native - No JavaScript required for basic functionality
Basic Usage
A basic dialog consists of a <dialog> element with inner structure for header, body, and footer.
Basic Dialog
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Dialog
</button>
<dialog class="dialog">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Dialog Title</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Confirm</button>
</div>
</div>
</dialog>Size Variants
Small Dialog
Perfect for simple confirmations and alerts.
Small Dialog
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Small Dialog
</button>
<dialog class="dialog dialog-sm">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Small Dialog</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">OK</button>
</div>
</div>
</dialog>Large Dialog
For dialogs with more content.
Large Dialog
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Large Dialog
</button>
<dialog class="dialog dialog-lg">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Large Dialog</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Continue</button>
</div>
</div>
</dialog>Extra Large Dialog
For complex forms or detailed content.
Extra Large Dialog
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Extra Large Dialog
</button>
<dialog class="dialog dialog-xl">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Extra Large Dialog</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">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" onclick="this.nextElementSibling.showModal()">
Open Fullscreen Dialog
</button>
<dialog class="dialog dialog-fullscreen">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Fullscreen Dialog</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Done</button>
</div>
</div>
</dialog>Dialog Variants
Alert Dialog
Simplified dialog for confirmations and critical actions.
Alert Dialog
<button class="btn btn-error" onclick="this.nextElementSibling.showModal()">
Delete Item
</button>
<dialog class="dialog dialog-sm">
<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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-error" onclick="this.closest('dialog').close()">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" onclick="this.nextElementSibling.showModal()">
Open Dialog with Dividers
</button>
<dialog class="dialog dialog-divider">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Settings</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Save</button>
</div>
</div>
</dialog>Footer Alignment
Right-Aligned (Default)
Right-Aligned Footer
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Dialog
</button>
<dialog class="dialog">
<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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Confirm</button>
</div>
</div>
</dialog>Left-Aligned Footer
Left-Aligned Footer
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Dialog
</button>
<dialog class="dialog">
<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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Confirm</button>
</div>
</div>
</dialog>Center-Aligned Footer
Center-Aligned Footer
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Dialog
</button>
<dialog class="dialog">
<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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Confirm</button>
</div>
</div>
</dialog>Space-Between Footer
Space-Between Footer
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Dialog
</button>
<dialog class="dialog">
<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" onclick="this.closest('dialog').close()">Cancel</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Confirm</button>
</div>
</div>
</dialog>Scrollable Dialog
For dialogs with long content, add the dialog-scrollable class:
Scrollable Dialog
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Scrollable Dialog
</button>
<dialog class="dialog dialog-scrollable">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Terms and Conditions</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">Decline</button>
<button class="btn btn-primary" onclick="this.closest('dialog').close()">Accept</button>
</div>
</div>
</dialog>Position Variants
Top Position
Top Positioned Dialog
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Top Dialog
</button>
<dialog class="dialog dialog-top">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Top Dialog</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">OK</button>
</div>
</div>
</dialog>Bottom Position
Bottom Positioned Dialog
<button class="btn btn-primary" onclick="this.nextElementSibling.showModal()">
Open Bottom Dialog
</button>
<dialog class="dialog dialog-bottom">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Bottom Dialog</h2>
<button class="dialog-close" onclick="this.closest('dialog').close()" 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" onclick="this.closest('dialog').close()">OK</button>
</div>
</div>
</dialog>JavaScript Integration
The native <dialog> element provides built-in methods:
// Get dialog reference
const dialog = document.querySelector('dialog');
// Open as modal (with backdrop)
dialog.showModal();
// Open as non-modal (no backdrop)
dialog.show();
// Close dialog
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');
});
// Close on backdrop click
dialog.addEventListener('click', (e) => {
if (e.target === dialog) {
dialog.close();
}
});
Complete Example
JavaScript Integration Example
<button class="btn btn-primary" id="openDialogBtn">Open Dialog</button>
<dialog class="dialog" id="myDialog">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">Interactive Dialog</h2>
<button class="dialog-close" aria-label="Close dialog">ร</button>
</div>
<div class="dialog-body">
<p>This dialog uses JavaScript for all interactions.</p>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" data-action="cancel">Cancel</button>
<button class="btn btn-primary" data-action="confirm">Confirm</button>
</div>
</div>
</dialog>
<script>
const dialog = document.getElementById('myDialog');
const openBtn = document.getElementById('openDialogBtn');
// Open dialog
openBtn.addEventListener('click', () => dialog.showModal());
// Close on backdrop click
dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.close();
});
// Handle all close buttons
dialog.querySelectorAll('[data-action], .dialog-close').forEach(btn => {
btn.addEventListener('click', () => dialog.close());
});
</script>Framework Examples
React
import { useRef } from 'react';
interface DialogProps {
title: string;
children: React.ReactNode;
onClose?: () => void;
}
export function Dialog({ title, children, onClose }: DialogProps) {
const dialogRef = useRef<HTMLDialogElement>(null);
const openDialog = () => dialogRef.current?.showModal();
const closeDialog = () => {
dialogRef.current?.close();
onClose?.();
};
return (
<>
<button className="btn btn-primary" onClick={openDialog}>
Open Dialog
</button>
<dialog
ref={dialogRef}
className="dialog"
onClick={(e) => e.target === e.currentTarget && closeDialog()}
>
<div className="dialog-box">
<div className="dialog-header">
<h2 className="dialog-title">{title}</h2>
<button className="dialog-close" onClick={closeDialog} aria-label="Close">
ร
</button>
</div>
<div className="dialog-body">{children}</div>
<div className="dialog-footer">
<button className="btn btn-ghost" onClick={closeDialog}>Cancel</button>
<button className="btn btn-primary" onClick={closeDialog}>Confirm</button>
</div>
</div>
</dialog>
</>
);
}
Vue
<template>
<button class="btn btn-primary" @click="openDialog">Open Dialog</button>
<dialog ref="dialogRef" class="dialog" @click.self="closeDialog">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title">{{ title }}</h2>
<button class="dialog-close" @click="closeDialog" aria-label="Close">ร</button>
</div>
<div class="dialog-body">
<slot />
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" @click="closeDialog">Cancel</button>
<button class="btn btn-primary" @click="closeDialog">Confirm</button>
</div>
</div>
</dialog>
</template>
<script setup>
import { ref } from 'vue';
defineProps({ title: String });
const dialogRef = ref(null);
const openDialog = () => dialogRef.value?.showModal();
const closeDialog = () => dialogRef.value?.close();
</script>
Best Practices
Accessibility
The native <dialog> element handles most accessibility concerns automatically:
- 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
- ARIA attributes - The dialog has implicit
role="dialog"andaria-modal="true"
Add descriptive labels for additional accessibility:
<dialog class="dialog" aria-labelledby="dialogTitle" aria-describedby="dialogDesc">
<div class="dialog-box">
<div class="dialog-header">
<h2 class="dialog-title" id="dialogTitle">Confirm Action</h2>
</div>
<div class="dialog-body">
<p id="dialogDesc">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 - The native dialog supports Escape key and backdrop clicks
- Consider mobile - Use fullscreen variant on small screens
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 |
Native Dialog Methods
| 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() |