Headless modal dialog component for vanilla JavaScript. Accessible, unstyled, tiny.
npm install @data-slot/dialogHeadless modal dialog component for vanilla JavaScript. Accessible, unstyled, tiny.
``bash`
npm install @data-slot/dialog
` Dialog description text.html
Dialog Title
`
Auto-discover and bind all dialog instances in a scope (defaults to document).
`typescript
import { create } from "@data-slot/dialog";
const controllers = create(); // Returns DialogController[]
`
Create a controller for a specific element.
`typescript
import { createDialog } from "@data-slot/dialog";
const dialog = createDialog(element, {
defaultOpen: false,
closeOnClickOutside: true,
closeOnEscape: true,
lockScroll: true,
onOpenChange: (open) => console.log(open),
});
`
| 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 |
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.
`html
...
$3
| 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
`html
Title
Description
`$3
-
dialog-content - The dialog panel (required)$3
-
dialog-trigger - Button to open the dialog
- dialog-title - Title for aria-labelledby
- dialog-description - Description for aria-describedby
- dialog-close - Button to close the dialogStyling
Use
data-state attributes for CSS styling:`css
/ 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;
}
`Stacking is intentionally not hardcoded in JavaScript. Configure
z-index in your CSS.
When multiple dialogs are open, data-stack-index and CSS variables are exposed on
dialog-overlay and dialog-content:
- data-stack-index
- --dialog-stack-index
- --dialog-overlay-stack-index (overlay)
- --dialog-content-stack-index (content)With Tailwind:
`html
`Accessibility
The component automatically handles:
-
role="dialog" on content
- aria-modal="true" on content
- aria-labelledby linked to title
- aria-describedby linked to description
- aria-haspopup="dialog" on trigger
- aria-expanded state on trigger
- Focus trap within dialog
- Focus restoration on closeKeyboard Navigation
| Key | Action |
|-----|--------|
|
Escape | Close dialog |
| Tab | Cycle focus within dialog |
| Shift+Tab | Cycle focus backwards |Events
$3
Listen for changes via custom events:
`javascript
element.addEventListener("dialog:change", (e) => {
console.log("Dialog open:", e.detail.open);
});
`$3
Control the dialog via events:
| Event | Detail | Description |
|-------|--------|-------------|
|
dialog:set | { open: boolean } | Set open state programmatically |`javascript
// Open the dialog
element.dispatchEvent(
new CustomEvent("dialog:set", { detail: { open: true } })
);// Close the dialog
element.dispatchEvent(
new CustomEvent("dialog:set", { detail: { open: false } })
);
`#### Deprecated Shapes
The following shape is deprecated and will be removed in v1.0:
`javascript
// Deprecated: { value: boolean }
element.dispatchEvent(
new CustomEvent("dialog:set", { detail: { value: true } })
);
`Use
{ open: boolean }` instead.MIT