oriUI

useDisclosure

A headless show / hide primitive for disclosure widgets — expandable panels, accordions, menus, the collapsible sidebar groups on this very site. It owns the open state, the WAI-ARIA wiring, and the toggle handler; you own the markup and the styles.

This is the Vue binding of the headless layer; the framework-agnostic contract lives in @oriui/headless. Behavior is swappable per primitive through the OriHeadless adapter, and a zero-dependency native engine ships as the default — so it works with no setup.

Import

ts
import { useDisclosure } from '@oriui/headless/vue'

Options

Pass an options object — or a ref / getter returning one. disabled is live: it is re-read whenever the option changes, so you can bind it to state. id and defaultOpen are seeds — they are read once, when the primitive is created, and later changes to them are ignored:

OptionTypeDefaultDescription
defaultOpenbooleanfalseInitial open state (uncontrolled).
disabledbooleanfalseIgnore toggles while true.
idstringautoBase id for the trigger / content ARIA wiring.

Returns

useDisclosure returns a DisclosureControl — reactive state, ARIA-correct prop bags to spread onto your markup, and imperative handlers.

PropertyTypeDescription
openComputedRef<boolean>Current open state.
rootPropsComputedRef<object>Bind to the root wrapper (data-state).
triggerPropsComputedRef<object>Bind to the toggle control (aria-expanded, aria-controls, click, disabled).
contentPropsComputedRef<object>Bind to the collapsible content (id, role, aria-labelledby, hidden).
setOpen(open)(open: boolean) => voidImperatively set the state.
toggle()() => voidFlip the state.

Usage

Spread the prop bags onto your own elements — the primitive supplies the behavior, you supply the look:

vue
<script setup lang="ts">
import { useDisclosure } from '@oriui/headless/vue'

const { open, triggerProps, contentProps } = useDisclosure(() => ({ defaultOpen: false }))
</script>

<template>
    <button v-bind="triggerProps">Details {{ open ? '▲' : '▼' }}</button>
    <div v-bind="contentProps">…content…</div>
</template>

@oriui/css is framework-free: the same .ori-* classes and tokens style this markup in any framework.

Adapter

The behavior resolves through the OriHeadless contract. With nothing wired, useDisclosure falls back to the native @oriui/headless engine — zero dependencies, no setup. To swap in a different engine (Zag, or your own), provide a disclosure adapter once at the app root:

ts
import { OriHeadless } from '@oriui/headless/vue'
import { myDisclosureAdapter } from './headless/my-disclosure'

app.use(OriHeadless, { disclosure: myDisclosureAdapter })

The adapter must satisfy the DisclosureAdapter contract — same DisclosureControl shape — so your markup never changes when you swap engines. To scope an adapter to one subtree instead of the whole app, call provideHeadless({ disclosure: … }) inside that subtree's setup.

Accessibility

The prop bags carry the full WAI-ARIA Disclosure pattern, so the wiring is correct as long as you bind them:

  • triggerProps renders a real <button type="button"> with aria-expanded reflecting the open state and aria-controls pointing at the content. When disabled, it sets the real disabled attribute (plus data-disabled for styling).
  • contentProps sets role="region", aria-labelledby back to the trigger (so the region takes its accessible name from the toggle), and a real hidden attribute while collapsed — the content is removed from the accessibility tree and tab order when closed.
  • Clicking the trigger toggles the state; activation is the native button behavior, so Enter and Space both work with no extra handlers.
  • rootProps, triggerProps, and contentProps each expose a data-state of open / closed as a styling hook for transitions.
KeyAction
EnterToggles the disclosure (native button).
SpaceToggles the disclosure (native button).

See also

  • @oriui/headless — the framework-agnostic contract and the native engine behind this binding.
  • useDialog — the modal counterpart; the hard-behavior case delegated to an adapter.
  • CSS layer — standalone .ori-* classes for styling the markup you bind these props to.