Skip to content
data-slotv1.0.0
Esc
↑↓navigate↵open⌘Jpreview
On this page

Collapsible

A disclosure that shows and hides a single section of content.

@data-slot/collapsibleSource ↗
bun add @data-slot/collapsible
npm install @data-slot/collapsible
pnpm add @data-slot/collapsible

Anatomy

Both parts are required: a collapsible-trigger to toggle, and a collapsible-content to reveal.

<div data-slot="collapsible">
  <button data-slot="collapsible-trigger">Toggle</button>
  <div data-slot="collapsible-content">Content</div>
</div>

Examples

Show and hide a section

Preview

Collapsible is the simplest component — just a trigger and content.

Perfect for FAQ items, collapsible sections, or any show/hide pattern.

Collapsible is the simplest component — just a trigger and content.

Perfect for FAQ items, collapsible sections, or any show/hide pattern.

Show codeHide code
<div data-slot="collapsible">
  <button data-slot="collapsible-trigger" class="collapsible-trigger-btn">
    Show more details
  </button>
  <div class="collapsible-content-wrapper">
    <div data-slot="collapsible-content" class="collapsible-content">
      <div class="collapsible-content-inner">
        <p>Collapsible is the simplest component — just a trigger and content.</p>
        <p>Perfect for FAQ items, collapsible sections, or any show/hide pattern.</p>
      </div>
    </div>
  </div>
</div>

<style>
  .collapsible-trigger-btn {
    padding: 0.5rem 1rem;
    background: none;
    border: 1px dashed var(--border);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .collapsible-trigger-btn::before {
    content: "▶";
    font-size: 0.75rem;
    transition: transform 0.2s;
    line-height: 1rem;
  }
  .collapsible-trigger-btn[aria-expanded="true"]::before {
    transform: rotate(90deg);
  }
  .collapsible-content-wrapper {
    display: block;
  }

  .collapsible-content {
    overflow: hidden;
    min-height: 0;
    height: var(--collapsible-panel-height);
    transition: height 0.3s ease-out;
  }
  .collapsible-content[hidden] { display: block !important; }
  .collapsible-content-inner {
    padding: 1rem;
    margin-top: 0.5rem;
    background: var(--code-bg);
  }
.collapsible-content[data-closed],
.collapsible-content[data-starting-style],
.collapsible-content[data-ending-style] { height: 0; }
.collapsible-content-inner p { margin: 0; }
.collapsible-content-inner p + p { margin-top: 0.5rem; }
</style>
<div data-slot="collapsible" class="group" style="--disclosure-icon: '▶'">
  <button
    data-slot="collapsible-trigger"
    class="px-4 py-2 bg-transparent border border-dashed border-[var(--border)]
           cursor-pointer flex items-center gap-2 hover:bg-code-bg
           before:content-(--disclosure-icon) before:text-xs
           before:transition-transform before:duration-200
           aria-expanded:before:rotate-90"
  >
    Show more details
  </button>
  <div>
    <div data-slot="collapsible-content" class="overflow-hidden h-(--collapsible-panel-height) transition-all duration-300 ease-out data-closed:h-0 data-starting-style:h-0 data-ending-style:h-0">
      <div class="p-4 mt-2 bg-code-bg">
        <p>Collapsible is the simplest component — just a trigger and content.</p>
        <p class="mt-2">Perfect for FAQ items, collapsible sections, or any show/hide pattern.</p>
      </div>
    </div>
  </div>
</div>

Open by default

Start expanded with data-default-open.

Preview

Collapsible is the simplest component — just a trigger and content.

Perfect for FAQ items, collapsible sections, or any show/hide pattern.

Collapsible is the simplest component — just a trigger and content.

Perfect for FAQ items, collapsible sections, or any show/hide pattern.

Show codeHide code
<div data-slot="collapsible" data-default-open>
  <button data-slot="collapsible-trigger" class="collapsible-trigger-btn">
    Show more details
  </button>
  <div class="collapsible-content-wrapper">
    <div data-slot="collapsible-content" class="collapsible-content">
      <div class="collapsible-content-inner">
        <p>Collapsible is the simplest component — just a trigger and content.</p>
        <p>Perfect for FAQ items, collapsible sections, or any show/hide pattern.</p>
      </div>
    </div>
  </div>
</div>

<style>
  .collapsible-trigger-btn {
    padding: 0.5rem 1rem;
    background: none;
    border: 1px dashed var(--border);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .collapsible-trigger-btn::before {
    content: "▶";
    font-size: 0.75rem;
    transition: transform 0.2s;
    line-height: 1rem;
  }
  .collapsible-trigger-btn[aria-expanded="true"]::before {
    transform: rotate(90deg);
  }
  .collapsible-content-wrapper {
    display: block;
  }

  .collapsible-content {
    overflow: hidden;
    min-height: 0;
    height: var(--collapsible-panel-height);
    transition: height 0.3s ease-out;
  }
  .collapsible-content[hidden] { display: block !important; }
  .collapsible-content-inner {
    padding: 1rem;
    margin-top: 0.5rem;
    background: var(--code-bg);
  }
.collapsible-content[data-closed],
.collapsible-content[data-starting-style],
.collapsible-content[data-ending-style] { height: 0; }
.collapsible-content-inner p { margin: 0; }
.collapsible-content-inner p + p { margin-top: 0.5rem; }
</style>
<div data-slot="collapsible" data-default-open class="group" style="--disclosure-icon: '▶'">
  <button
    data-slot="collapsible-trigger"
    class="px-4 py-2 bg-transparent border border-dashed border-[var(--border)]
           cursor-pointer flex items-center gap-2 hover:bg-code-bg
           before:content-(--disclosure-icon) before:text-xs
           before:transition-transform before:duration-200
           aria-expanded:before:rotate-90"
  >
    Show more details
  </button>
  <div>
    <div data-slot="collapsible-content" class="overflow-hidden h-(--collapsible-panel-height) transition-all duration-300 ease-out data-closed:h-0 data-starting-style:h-0 data-ending-style:h-0">
      <div class="p-4 mt-2 bg-code-bg">
        <p>Collapsible is the simplest component — just a trigger and content.</p>
        <p class="mt-2">Perfect for FAQ items, collapsible sections, or any show/hide pattern.</p>
      </div>
    </div>
  </div>
</div>

API reference

Initialization

create(scope?)

Auto-discover and bind all collapsible instances in a scope (defaults to document).

import { create } from "@data-slot/collapsible";

const controllers = create(); // Returns CollapsibleController[]

createCollapsible(root, options?)

Create a controller for a specific element.

import { createCollapsible } from "@data-slot/collapsible";

const collapsible = createCollapsible(element, {
  defaultOpen: false,
  hiddenUntilFound: false,
  onOpenChange: (open) => console.log(open),
});

Slots

Runtime Slots

  • collapsible - Root element that manages the open state.
  • collapsible-trigger - Required button that toggles the content and receives aria-expanded.
  • collapsible-content - Required panel that is shown or hidden with the open state.

Markup

<div data-slot="collapsible">
  <button data-slot="collapsible-trigger">Toggle</button>
  <div data-slot="collapsible-content">Content</div>
</div>

Data Attributes

Options can also be set via data attributes on the root element. JS options take precedence.

Attribute Type Default Description
data-default-open boolean false Initial open state
data-hidden-until-found boolean false Use hidden="until-found" when closed

Boolean attributes: present or "true" = true, "false" = false, absent = default.

<!-- Start expanded -->
<div data-slot="collapsible" data-default-open>
  ...
</div>

Options

Option Type Default Description
defaultOpen boolean false Initial open state
hiddenUntilFound boolean false Use hidden="until-found" when closed
onOpenChange (open: boolean) => void undefined Callback when open state changes (not called on init)

Controller

Method/Property Description
open() Open the collapsible
close() Close the collapsible
toggle() Toggle the collapsible
isOpen Current open state (readonly boolean)
destroy() Cleanup all event listeners

Events

Outbound Events

Listen for changes via custom events:

element.addEventListener("collapsible:change", (e) => {
  console.log("Collapsible open:", e.detail.open);
});

Inbound Events

Control the collapsible via events:

Event Detail Description
collapsible:set { open: boolean } Set open state programmatically
// Open the collapsible
element.dispatchEvent(
  new CustomEvent("collapsible:set", { detail: { open: true } })
);

// Close the collapsible
element.dispatchEvent(
  new CustomEvent("collapsible:set", { detail: { open: false } })
);

Note: Blocked when trigger is disabled (has disabled attribute or aria-disabled="true").

Deprecated Shapes

The following shape is deprecated and will be removed in v1.0:

// Deprecated: { value: boolean }
element.dispatchEvent(
  new CustomEvent("collapsible:set", { detail: { value: true } })
);

Use { open: boolean } instead.

Styling

Use data-state attributes for CSS styling (available on both root and content):

/* Hidden state */
[data-slot="collapsible-content"][hidden] {
  display: none;
}

/* Or use data-state */
[data-slot="collapsible"][data-state="closed"] [data-slot="collapsible-content"] {
  display: none;
}

/* Animate using data-state on content */
[data-slot="collapsible-content"] {
  overflow: hidden;
  transition: max-height 0.3s;
  max-height: 0;
}

[data-slot="collapsible-content"][data-state="open"] {
  max-height: 500px;
}

/* Presence lifecycle markers */
[data-slot="collapsible-content"][data-starting-style] {
  opacity: 0;
}

[data-slot="collapsible-content"][data-ending-style] {
  opacity: 0;
}

CSS Variables

The content element exposes size variables you can use for dimension animations:

Variable Description
--collapsible-panel-height Panel height (auto at open rest, measured px during transitions, 0px when closed)
--collapsible-panel-width Panel width (auto at open rest, measured px during transitions, 0px when closed)

Example:

[data-slot="collapsible-content"] {
  overflow: hidden;
  height: var(--collapsible-panel-height);
  width: var(--collapsible-panel-width);
  transition: height 0.25s ease, width 0.25s ease, opacity 0.2s ease;
}

[data-slot="collapsible-content"][data-starting-style],
[data-slot="collapsible-content"][data-ending-style] {
  height: 0;
  opacity: 0;
}

Accessibility

The component automatically handles:

  • aria-expanded state on trigger
  • aria-controls linking trigger to content
  • role="region" on content
  • aria-labelledby linking content back to trigger
  • Unique ID generation for trigger and content
  • Disabled trigger support (respects disabled attribute and aria-disabled="true")

Behavior

Find-in-Page Support

Enable hiddenUntilFound (or data-hidden-until-found) to close panels with hidden="until-found". This allows browser find-in-page to reveal matching text.

With Tailwind:

<div data-slot="collapsible" class="group">
  <button data-slot="collapsible-trigger" class="flex items-center gap-2">
    <span>Show more</span>
    <svg class="group-data-[state=open]:rotate-180 transition-transform">...</svg>
  </button>
  <div data-slot="collapsible-content" class="hidden group-data-[state=open]:block">
    Content here
  </div>
</div>