@data-slot/dialog
v0.2.7
Published
Headless modal dialog component for vanilla JavaScript. Accessible, unstyled, tiny.
Maintainers
Readme
@data-slot/dialog
Headless modal dialog component for vanilla JavaScript. Accessible, unstyled, tiny.
Installation
npm install @data-slot/dialogQuick Start
<div data-slot="dialog">
<button data-slot="dialog-trigger">Open Dialog</button>
<div data-slot="dialog-content" hidden>
<h2 data-slot="dialog-title">Dialog Title</h2>
<p data-slot="dialog-description">Dialog description text.</p>
<button data-slot="dialog-close">Close</button>
</div>
</div>
<script type="module">
import { create } from "@data-slot/dialog";
const controllers = create();
</script>API
create(scope?)
Auto-discover and bind all dialog instances in a scope (defaults to document).
import { create } from "@data-slot/dialog";
const controllers = create(); // Returns DialogController[]createDialog(root, options?)
Create a controller for a specific element.
import { createDialog } from "@data-slot/dialog";
const dialog = createDialog(element, {
defaultOpen: false,
closeOnClickOutside: true,
closeOnEscape: true,
lockScroll: true,
onOpenChange: (open) => console.log(open),
});Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| defaultOpen | boolean | false | Initial open state |
| closeOnClickOutside | boolean | true | Close when clicking outside content |
| closeOnEscape | boolean | true | Close when pressing Escape |
| lockScroll | boolean | true | Lock body scroll when open |
| alertDialog | boolean | false | Use alertdialog role for confirmations |
| onOpenChange | (open: boolean) => void | undefined | Callback when open state changes |
Data Attributes
Options can also be set via data attributes on the root element. JS options take precedence over data attributes.
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| data-default-open | boolean | false | Initial open state |
| data-close-on-click-outside | boolean | true | Close when clicking outside content |
| data-close-on-escape | boolean | true | Close when pressing Escape |
| data-lock-scroll | boolean | true | Lock body scroll when open |
| data-alert-dialog | boolean | false | Use alertdialog role for confirmations |
Boolean attributes: present or "true" = true, "false" = false, absent = default.
<!-- Disable close on Escape -->
<div data-slot="dialog" data-close-on-escape="false">
...
</div>
<!-- Alert dialog that stays open when clicking outside -->
<div data-slot="dialog" data-alert-dialog data-close-on-click-outside="false">
...
</div>Controller
| Method/Property | Description |
|-----------------|-------------|
| open() | Open the dialog |
| close() | Close the dialog |
| toggle() | Toggle the dialog |
| isOpen | Current open state (readonly boolean) |
| destroy() | Cleanup all event listeners |
Markup Structure
<div data-slot="dialog">
<button data-slot="dialog-trigger">Open</button>
<div data-slot="dialog-content" role="dialog">
<h2 data-slot="dialog-title">Title</h2>
<p data-slot="dialog-description">Description</p>
<button data-slot="dialog-close">Close</button>
</div>
</div>Required Slots
dialog-content- The dialog panel (required)
Optional Slots
dialog-trigger- Button to open the dialogdialog-title- Title foraria-labelledbydialog-description- Description foraria-describedbydialog-close- Button to close the dialog
Styling
Use data-state attributes for CSS styling:
/* Backdrop/overlay */
[data-slot="dialog-content"] {
position: fixed;
inset: 0;
display: grid;
place-items: center;
background: rgba(0, 0, 0, 0.5);
}
/* Closed state */
[data-slot="dialog"][data-state="closed"] [data-slot="dialog-content"] {
display: none;
}
/* Animation */
[data-slot="dialog-content"] {
opacity: 0;
transition: opacity 0.2s;
}
[data-slot="dialog"][data-state="open"] [data-slot="dialog-content"] {
opacity: 1;
}With Tailwind:
<div data-slot="dialog-content" class="fixed inset-0 grid place-items-center bg-black/50 data-[state=closed]:hidden">
<div class="bg-white rounded-lg p-6 max-w-md">
<!-- Dialog content -->
</div>
</div>Accessibility
The component automatically handles:
role="dialog"on contentaria-modal="true"on contentaria-labelledbylinked to titlearia-describedbylinked to descriptionaria-haspopup="dialog"on triggeraria-expandedstate on trigger- Focus trap within dialog
- Focus restoration on close
Keyboard Navigation
| Key | Action |
|-----|--------|
| Escape | Close dialog |
| Tab | Cycle focus within dialog |
| Shift+Tab | Cycle focus backwards |
Events
Listen for changes via custom events:
element.addEventListener("dialog:change", (e) => {
console.log("Dialog open:", e.detail.open);
});License
MIT
