Skip to content

I9kCollapsible ​

Native, independently expanding disclosure surface with slots for rich summary and body content.

View as MarkdownView source
ts
import { I9kCollapsible } from '@9klabs/design';

Examples ​

Closed ​

Course module

Module lessons and resources.

vue
<I9kCollapsible>
  <template #summary>Course module</template>
  <p>Module lessons and resources.</p>
</I9kCollapsible>

Initially open ​

Course module

This body is visible when the example first renders.

vue
<I9kCollapsible :default-open="true">
  <template #summary>Course module</template>
  <p>This body is visible when the example first renders.</p>
</I9kCollapsible>

Rich summary ​

Module 03 · 9 topics
  1. Plan
  2. Design
  3. Build
vue
<I9kCollapsible>
  <template #summary><span><strong>Module 03</strong> · 9 topics</span></template>
  <ol><li>Plan</li><li>Design</li><li>Build</li></ol>
</I9kCollapsible>

Flush, in a list ​

01 · Getting started
  1. Welcome
  2. Setup
02 · Prompting
  1. Context
vue
<div style="display: grid; gap: var(--spacing-2); max-width: 20rem">
  <I9kCollapsible variant="flush" :default-open="true">
    <template #summary>01 · Getting started</template>
    <ol><li>Welcome</li><li>Setup</li></ol>
  </I9kCollapsible>
  <I9kCollapsible variant="flush">
    <template #summary>02 · Prompting</template>
    <ol><li>Context</li></ol>
  </I9kCollapsible>
</div>

Independent instances ​

First

Open together.

Second

Also open.

vue
<div style="display: grid; gap: var(--spacing-6)">
  <I9kCollapsible :default-open="true"><template #summary>First</template><p>Open together.</p></I9kCollapsible>
  <I9kCollapsible :default-open="true"><template #summary>Second</template><p>Also open.</p></I9kCollapsible>
</div>

Controlled ​

Course module

The parent owns whether this body is visible.

vue
<button type="button" @click="open = !open">{{ open ? 'Collapse' : 'Expand' }}</button>
<I9kCollapsible v-model:open="open">
  <template #summary>Course module</template>
  <p>The parent owns whether this body is visible.</p>
</I9kCollapsible>

Arabic RTL ​

الوحدة الأولى

محتوى الوحدة وتفاصيلها.

vue
<div dir="rtl" lang="ar">
  <I9kCollapsible :default-open="true">
    <template #summary>الوحدة الأولى</template>
    <p>محتوى الوحدة وتفاصيلها.</p>
  </I9kCollapsible>
</div>

Watch out ​

  • defaultOpen controls only the initial state; bind v-model:open for a controlled disclosure such as expand-all.
  • A one-way :open binding does not learn about user toggles; use v-model:open so the parent value stays in step.
  • Uncontrolled sibling instances expand independently; control them with v-model:open if only one section may be open.
  • Provide visible summary content because the summary slot is the disclosure control label.
  • Use variant="flush" for disclosures listed inside a panel or sidebar; the default card is meant to stand on the page.

API ​

Props ​

PropTypeDefaultRequired
defaultOpenbooleanfalseno
openbooleanundefinedno
variant'card' | 'flush''card'no

Emits ​

EventPayload
toggle[open: boolean]
update:open[open: boolean]

Slots ​

  • summary
  • default

Agent prompt ​

Paste this into an agent to have it use the component correctly. It is self-contained.

text
Use I9kCollapsible from @9klabs/design for a single branded disclosure that may contain rich Vue markup.

import { I9kCollapsible } from '@9klabs/design';

Props:
- defaultOpen?: boolean (default false) — sets only the initial native open state when `open` is not bound.
- open?: boolean (default undefined) — bind with v-model:open to control the open state from the parent, e.g. an expand-all / collapse-all button. Leave it unbound for the browser-managed, uncontrolled behavior.
- variant?: 'card' | 'flush' (default 'card') — `flush` drops the border and background, for a list of disclosures inside a panel or a sidebar.

Emits:
- toggle(open: boolean) — reports the current native details.open value after every native toggle, including one caused by changing a bound `open`.
- update:open(open: boolean) — emitted only when `open` is bound and a user toggle makes the native state differ from it.

Slots:
- summary — content rendered inside the native <summary>.
- default — content rendered in the disclosure body.

Behavior: the component renders native <details>/<summary>. Unless `open` is bound, each instance owns its browser-managed state, so siblings expand independently and any number may stay open. When `open` is bound, changing it opens or closes the disclosure, and a user toggle is reported through update:open.

IMPORTANT: `defaultOpen` is only an initial-state option; bind `open` (v-model:open) when the parent must set the state, such as expand-all. A bound `open` without a listener for update:open does not follow user toggles back into the parent — use v-model:open, not a one-way :open.

Usage:
<I9kCollapsible :default-open="true" @toggle="(open) => console.log(open)">
  <template #summary><strong>Module 01</strong> · 7 topics</template>
  <ol><li>What is coding?</li><li>Engineering judgment</li></ol>
</I9kCollapsible>

Made with love 💚, caffeine, and Claude Code. Built with @9klabs/design itself.