Components

Disclosure

Toggle visibility of associated content from a trigger.

Release Scope: Canonical Implementation: Core Site Visibility: Listed Adapters: React, Vanilla, Solid, Vue, Svelte Category: disclosure

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.

Delivery window

Orders are processed within 1-2 business days.

defaultOpen reveals important context on first render while staying 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.

Filter scope

Open state is synchronized with an external button and state label.

Use this pattern when disclosure state must coordinate with URL state, analytics, or other widgets.

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.

Availability

This section is locked for your current plan.

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.

Security overview

Manage password rotation, 2FA, and recovery options in one section.

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.

FAQ

Compose multiple disclosure items to build an FAQ group with predictable semantics.

No, disclosure is headless and only provides behavior and semantics.

Yes, sync open state with external events and call controller.setOpen().

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.

Specification

Material
100% cotton
Fit
Regular
Care
Machine wash cold

Panel can host rich semantic content instead of plain text only.

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.

Environment

Current target: Production

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

  • root
  • trigger
  • content

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_SLOTS value
  • createDisclosureContractSnapshot value
  • createDisclosureController value
  • DisclosureContractSnapshot type
  • DisclosureController type
  • DisclosureControllerOptions type
  • DisclosureSlot type
  • DisclosureState type

Vanilla

  • bindDisclosure function
  • connectDisclosure function
  • DisclosureBinding interface
  • DisclosureElements interface