@magic-spells/dialog-panel
v1.0.1
Published
A lightweight web component wrapper for native <dialog> elements with state-driven animations.
Downloads
138
Maintainers
Readme
Dialog Panel
A lightweight web component wrapper for native <dialog> elements with state-driven animations.
Features
- Zero dependencies - Uses native
<dialog>for focus trapping and accessibility - Lightweight - ~3.4kb minified
- State-driven animations - CSS transitions based on
stateattribute - Cross-browser - Custom
<dialog-backdrop>for consistent animations (including Firefox) - Accessible - Native dialog handles focus trap, escape key, and ARIA
- Nested dialogs - Proper stacking and event isolation
Installation
npm install @magic-spells/dialog-panel// Import for side effects (registers custom elements)
import '@magic-spells/dialog-panel';Or include directly in HTML:
<script src="https://unpkg.com/@magic-spells/dialog-panel"></script>
<link rel="stylesheet" href="https://unpkg.com/@magic-spells/dialog-panel/css/min" />Usage
<button id="open-btn">Open Dialog</button>
<dialog-panel id="my-dialog">
<dialog aria-labelledby="dialog-title">
<div class="dialog-content">
<h2 id="dialog-title">Hello!</h2>
<p>This is a modal dialog.</p>
<button data-action-hide-dialog>Close</button>
</div>
</dialog>
</dialog-panel>
<script>
const dialog = document.getElementById('my-dialog');
document.getElementById('open-btn').addEventListener('click', (e) => {
dialog.show(e.target);
});
</script>How It Works
- Wrap a native
<dialog>element inside<dialog-panel> - Call
show(triggerElement)to open with animation - The
stateattribute transitions:hidden→showing→shown - Close via:
- Clicking backdrop
- Pressing Escape
- Clicking any element with
data-action-hide-dialog - Calling
hide()
- The
stateattribute transitions:shown→hiding→hidden - Focus returns to the trigger element
State Machine
hidden → showing → shown → hiding → hiddenThe state attribute on <dialog-panel> drives all CSS animations:
dialog-panel[state='showing'] > dialog { /* entering */ }
dialog-panel[state='shown'] > dialog { /* fully visible */ }
dialog-panel[state='hiding'] > dialog { /* exiting */ }
dialog-panel[state='hidden'] > dialog { /* hidden */ }API
Methods
| Method | Description |
|--------|-------------|
| show(triggerEl?) | Opens the dialog. Pass trigger element for focus return. Returns false if cancelled. |
| hide(triggerEl?) | Closes the dialog. Returns false if cancelled. |
Properties (read-only)
| Property | Type | Description |
|----------|------|-------------|
| state | string | Current state: 'hidden', 'showing', 'shown', 'hiding' |
| dialog | HTMLDialogElement | Reference to inner <dialog> |
| isOpen | boolean | true if showing or shown |
| triggerElement | HTMLElement \| null | Element that triggered the current action |
Events
| Event | Cancelable | Description |
|-------|------------|-------------|
| beforeShow | Yes | Fired before showing. Call preventDefault() to cancel. |
| shown | No | Fired after show animation completes. |
| beforeHide | Yes | Fired before hiding. Call preventDefault() to cancel. |
| hidden | No | Fired after hide animation completes. |
Event detail includes:
triggerElement- Element that triggered the actionresult- Value fromdata-resultattribute (if any)state- Current state
Data Attributes
| Attribute | Description |
|-----------|-------------|
| data-action-hide-dialog | Clicking this element closes the dialog |
| data-result | Value passed to hidden event when this element triggers close |
Styling
Default CSS
Import the default styles:
import '@magic-spells/dialog-panel';
import '@magic-spells/dialog-panel/css';Or in HTML:
<link rel="stylesheet" href="https://unpkg.com/@magic-spells/dialog-panel/css/min" />Custom Styling
Override styles using the state attribute:
/* Custom backdrop */
dialog-backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
}
/* Custom dialog appearance */
dialog-panel > dialog {
border-radius: 1rem;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
}
/* Custom animations */
dialog-panel[state='shown'] > dialog {
opacity: 1;
transform: scale(1);
transition: all 0.3s ease-out;
}
dialog-panel[state='hiding'] > dialog {
opacity: 0;
transform: scale(0.95);
transition: all 0.2s ease-in;
}Nested Dialogs
Dialogs can be nested inside other dialogs:
<dialog-panel id="outer">
<dialog>
<p>Outer dialog content</p>
<button id="open-inner">Open Inner</button>
<dialog-panel id="inner">
<dialog>
<p>Inner dialog content</p>
<button data-action-hide-dialog>Close Inner</button>
</dialog>
</dialog-panel>
</dialog>
</dialog-panel>Each dialog manages its own backdrop and events independently.
Dialog Backdrop
The <dialog-backdrop> element is auto-created if not present. It provides:
- Consistent cross-browser animations (native
::backdropdoesn't animate in Firefox) - Click-to-close functionality
- Custom styling support
Preventing Close
Use beforeHide to prevent closing:
dialog.addEventListener('beforeHide', (e) => {
if (formHasUnsavedChanges) {
e.preventDefault();
showConfirmation();
}
});Result Values
Track which button closed the dialog:
<button data-action-hide-dialog data-result="save">Save</button>
<button data-action-hide-dialog data-result="cancel">Cancel</button>dialog.addEventListener('hidden', (e) => {
if (e.detail.result === 'save') {
saveData();
}
});Browser Support
Modern browsers with support for:
- Custom Elements v1
- Native
<dialog>element - CSS
:has()selector
License
MIT
