Components
Disclosure
Toggle visibility of associated content from a trigger.
1. Live Preview
vanilla live preview activates on the client; source HTML keeps a readable preview island and primitive contract.
vanilla disclosure 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 { bindDisclosure } from "@nake-ui/adapter-vanilla";
const binding = bindDisclosure(nodes, items, options);
binding.destroy();
4. Examples
Default Open
Use defaultOpen to show important context on first render while keeping it collapsible.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDisclosure } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDisclosure(
{ root, trigger, content },
{ defaultOpen: true }
);
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Shipping information";
Controlled
Synchronize disclosure open state with an external button and status text.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDisclosure } from "@nake-ui/adapter-vanilla";
let open = true;
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDisclosure(
{ root, trigger, content },
{
defaultOpen: open,
onOpenChange(nextOpen) {
open = nextOpen;
state.textContent = open ? "Open" : "Closed";
}
}
);
externalToggle.addEventListener("click", () => {
open = !open;
binding.controller.setOpen(open);
binding.sync();
});
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Advanced filters";
Disabled
Demonstrate trigger/content behavior when disclosure is disabled.
Disabled disclosure does not toggle state.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDisclosure } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDisclosure(
{ root, trigger, content },
{ defaultOpen: true, disabled: true }
);
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Enterprise settings";
With Heading
Wrap trigger with a heading element to keep explicit document structure semantics.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDisclosure } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDisclosure({ root, trigger, content });
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Account security";
FAQ Group
Compose multiple disclosure items to build FAQ-style collapsible groups.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDisclosure } from "@nake-ui/adapter-vanilla";
const bindings = faqRoots.map((faqRoot) =>
bindDisclosure({
root: faqRoot,
trigger: faqRoot.querySelector("[data-demo-slot=faq-item-trigger]")!,
content: faqRoot.querySelector("[data-demo-slot=faq-item-content]")!
})
);
// cleanup on unmount
for (const binding of bindings) {
binding.destroy();
}
// q-1: Is disclosure styled by default? -> No, disclosure is headless and only provides behavior and semantics.
// q-2: Can disclosure be controlled? -> Yes, sync open state with external events and call controller.setOpen().
Rich Content
Render rich panel content such as definition lists without changing disclosure semantics.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDisclosure } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDisclosure({ root, trigger, content });
// panel can contain rich semantic nodes
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Product details";
Read-only
readOnly shows current state while preventing user toggling, useful for audit/preview views.
readOnly keeps state visible while preventing user-initiated toggling.
import "@nake-ui/theme-vanilla/styles.css";
import { bindDisclosure } from "@nake-ui/adapter-vanilla";
if (typeof window !== "undefined" && typeof document !== "undefined") {
const binding = bindDisclosure(
{ root, trigger, content },
{ defaultOpen: true }
);
// read-only gate: keep visible state but block user toggling
trigger.setAttribute("aria-disabled", "true");
trigger.addEventListener("click", preventToggle, true);
trigger.addEventListener("keydown", preventToggle, true);
// cleanup on unmount
binding.destroy();
}
trigger.textContent = "Deployment summary";
5. RTL
The same structure running inside a `dir="rtl"` container.
vanilla disclosure 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
Toggle visibility of associated content from a trigger.
- Visual accordion styling
- Animation ownership
8. Anatomy & Slot
roottriggercontent
9. DOM Contract
<section data-ui="disclosure" data-slot="root" data-state="closed">
<button data-slot="trigger" aria-expanded="false" aria-controls="x-content"></button>
<div id="x-content" data-slot="content" hidden></div>
</section>
10. State & Events
State Model
open/closed- controlled:
open+onOpenChange - uncontrolled:
defaultOpen
Event Model
- trigger click toggles state
- Enter/Space on trigger toggles state
11. Keyboard & Focus
- Enter/Space activates trigger
- focus stays on trigger unless host app moves focus
12. ARIA
- trigger:
aria-expanded,aria-controls - content:
aria-labelledby(recommended)
13. Validator Rules
-
structure.missing-required-slot -
aria.broken-controls-linkage -
dom.invalid-data-state-value
14. Adapter Notes
Vanilla
- Primary binder: bindDisclosure.
- Binds explicit DOM nodes and should be cleaned up on unmount.
15. Example Mapping
- No direct example mapping yet.
16. API
Core
-
DISCLOSURE_SLOTSvalue -
createDisclosureContractSnapshotvalue -
createDisclosureControllervalue -
DisclosureContractSnapshottype -
DisclosureControllertype -
DisclosureControllerOptionstype -
DisclosureSlottype -
DisclosureStatetype
Vanilla
-
bindDisclosurefunction -
connectDisclosurefunction -
DisclosureBindinginterface -
DisclosureElementsinterface