Components
Dialog
Provide modal or non-modal surface with lifecycle, dismiss, focus, and layered interaction behavior.
1. Live Preview
vanilla live preview activates on the client; source HTML keeps a readable preview island and primitive contract.
vanilla dialog preview activates on the client.
2. Installation
Install adapter-specific dependencies first, then complete setup via the installation guide. installation docs
pnpm add @nake-ui/primitives @nake-ui/adapter-vanilla @nake-ui/theme-vanilla
3. Usage
Minimal runnable usage for the current adapter.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDialog } from "@nake-ui/adapter-vanilla";
const binding = bindDialog(nodes, items, options);
binding.destroy();
4. Examples
NestedDialog
Use nested dialog bindings so parent and nested layers keep independent open, focus, and dismiss lifecycles.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDialog } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const parent = bindDialog(
{ trigger: parentTrigger, content: parentContent, overlay: parentOverlay, close: parentClose },
{
defaultOpen: false,
modal: true,
onOpenChange(nextOpen) {
if (!nextOpen && child.controller.getOpen()) {
child.controller.close();
child.sync();
}
}
}
);
const child = bindDialog(
{ trigger: childTrigger, content: childContent, overlay: childOverlay, close: childClose },
{
defaultOpen: false,
modal: true,
onOpenChange(nextOpen) {
if (nextOpen && !parent.controller.getOpen()) {
parent.controller.open();
parent.sync();
}
}
}
);
// cleanup on unmount
parent.destroy();
child.destroy();
}
parentTrigger.textContent = "Open parent dialog";
childTrigger.textContent = "Open nested dialog";
Custom Close Button
Compose a custom close button while preserving headless dialog close semantics.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDialog } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDialog(
{ trigger, content, overlay, title, description, close },
{ defaultOpen: false, modal: true }
);
// cleanup on unmount
binding.destroy();
}
close.textContent = "Dismiss";
No Close Button
Without a close button, dialog can still dismiss via overlay click, Escape, and outside dismiss.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDialog } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDialog(
{ trigger, content, overlay, title, description },
{ defaultOpen: false, modal: true }
);
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Open dialog";
Scrollable
Keep long dialog content usable with max-height and overflow scrolling on content.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDialog } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDialog(
{ trigger, content, overlay, title, description, close },
{ defaultOpen: false, modal: true }
);
content.style.maxHeight = "12rem";
content.style.overflow = "auto";
// cleanup on unmount
binding.destroy();
}
close.textContent = "Close";
5. RTL
The same structure running inside a `dir="rtl"` container.
vanilla dialog preview activates on the client.
6. Closure Coverage
Complete
Closure Score: 100% (19/19)
Required Checks
-
schemaRegistered -
specDocumented -
reactMapper -
siteExampleCoverage -
siteA11yCoverage -
siteVisible -
schemaValidatorCoverage -
schemaAriaCoverage -
specValidatorRulesSection -
specTestChecklistSection -
specSsrHydrationSection -
reactSsrHydrationCoverage -
reactRenderLevelSsrCoverage -
solidSsrCoverage -
coreImplemented -
primitiveUnitTest -
domContractSnapshot -
vanillaBinding -
adapterParitySnapshot
All required closure checks are currently satisfied.
7. Purpose / Non-goals
Provide modal or non-modal surface with lifecycle, dismiss, focus, and layered interaction behavior.
- Visual overlay styling
- Animation timeline management
- Layout system ownership
8. Anatomy & Slot
triggeroverlaycontenttitledescription(optional)close
9. DOM Contract
<button
data-ui="dialog"
data-slot="trigger"
data-state="open"
aria-haspopup="dialog"
aria-expanded="true"
aria-controls="dialog-content"
>
Open dialog
</button>
<div data-slot="overlay" data-state="open"></div>
<section
id="dialog-content"
data-slot="content"
data-state="open"
data-expanded="true"
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
>
<h2 id="dialog-title" data-slot="title">Dialog title</h2>
<p id="dialog-description" data-slot="description">Dialog description</p>
<button data-slot="close" aria-controls="dialog-content">Close</button>
</section>
10. State & Events
State Model
- open state reflected as
data-state="open|closed"on trigger/overlay/content - expanded state reflected as
data-expanded="true|false"on content - controlled mode:
open+onOpenChange - uncontrolled mode:
defaultOpen - modal flag:
modal=true|falsecontrols focus trap andaria-modal - nested dialogs keep independent open state and lifecycle ownership per layer
Event Model
- trigger click toggles open/closed
- close slot click closes
- overlay click closes
- outside
pointerdownand outsidefocusindismiss active dialog layer Escapedismisses only the top-most active layer- nested behavior: opening child dialog must not dismiss parent; outside/Escape closes child first, then parent on next interaction
11. Keyboard & Focus
- modal mode traps focus in active content
- stack-aware layering pauses parent trap while child layer is active
- closing a layer restores focus to the layer trigger
- nested close flow is LIFO (child first, parent second)
12. ARIA
- content:
role=dialog - content:
aria-modal(truein modal mode,falsein non-modal mode) - content:
aria-labelledby,aria-describedby - trigger:
aria-haspopup=dialog,aria-expanded,aria-controls
13. Validator Rules
-
structure.missing-required-slot -
aria.broken-controls-linkage -
focus.trap-missing -
focus.unrestored-on-close -
keyboard.unhandled-escape -
state.invalid-open-state -
dom.missing-public-data-attr
14. Adapter Notes
Vanilla
- Primary binder: bindDialog.
- Binds explicit DOM nodes and should be cleaned up on unmount.
15. Example Mapping
16. API
Core
-
createDialogControllervalue -
DialogControllertype -
DialogControllerOptionstype
Vanilla
-
bindDialogfunction -
DialogElementsinterface