Skip to content

Collapsible

A disclosure widget that toggles visibility of a content panel. It animates open and close transitions using --collapsible-panel-height and --collapsible-panel-width CSS custom properties, supports hidden="until-found" for browser find-in-page, and exposes open state via events and a controller.

import { Collapsible } from "@areia/slots";

document.querySelector("#root")!.innerHTML = `
  <div data-slot="collapsible" class="w-72">
    <button data-slot="collapsible-trigger" class="win95-button">
      Toggle
    </button>
    <div data-slot="collapsible-content" class="win95-inset mt-2 p-3">
      Content revealed when the trigger is activated.
    </div>
  </div>
`;

const root = document.querySelector(
  '[data-slot="collapsible"]',
)!;
const controller = Collapsible.createCollapsible(root, {
  defaultOpen: false,
});

Import

import { Collapsible } from "@areia/slots";

Usage

import { Collapsible } from "@areia/slots";

// Auto-bind a single root
const root = document.querySelector(
  '[data-slot="collapsible"]',
)!;
const controller = Collapsible.createCollapsible(root, {
  defaultOpen: false,
  onOpenChange: (open) => console.log("open changed:", open),
});

// Auto-bind all unbound roots in scope
const controllers = Collapsible.create();

Expected Markup

The collapsible expects a trigger button and a content panel inside the root.

<div data-slot="collapsible">
  <button data-slot="collapsible-trigger">Toggle</button>
  <div data-slot="collapsible-content">
    Content revealed when the trigger is activated.
  </div>
</div>

Data Slots

SlotRequiredDescription
collapsibleYesRoot container.
collapsible-triggerYesButton that toggles the content panel.
collapsible-contentYesPanel whose visibility is controlled.

CSS Custom Properties

The controller sets the following CSS custom properties on the content element to enable smooth open/close animations. During transitions the values are set to the content's pixel dimensions, and reset to auto once the transition settles.

PropertyDescription
--collapsible-panel-heightPixels during open/close transition, auto when settled.
--collapsible-panel-widthPixels during open/close transition, auto when settled.

Use these variables to animate height, width, block-size, or inline-size via CSS transitions:

[data-slot="collapsible-content"] {
  overflow: hidden;
  height: var(--collapsible-panel-height);
  width: var(--collapsible-panel-width);
  transition:
    height 200ms ease-out,
    width 200ms ease-out;
}

Generated Attributes

AttributeElement(s)Description
data-stateRoot, content"open" or "closed".
aria-expandedTrigger"true" when open, "false" when closed.
aria-controlsTriggerPoints to the content element id.
aria-labelledbyContentPoints to the trigger element id.
roleContentAlways set to "region".
hiddenContentSet to true when closed (or "until-found" when hiddenUntilFound is enabled).

Events

Outbound

EventDetailDescription
collapsible:change{ open: boolean }Fired on the root when the open state changes.

Inbound

EventDetailDescription
collapsible:set{ open: boolean }Dispatch on the root to programmatically set the open state.

API Reference

Options

OptionTypeDefaultDescription
defaultOpenbooleanfalseInitial open state.
hiddenUntilFoundbooleanfalseUse hidden="until-found" instead of hidden when closed, so browser find-in-page can reveal content.
onOpenChange(open: boolean) => voidCalled when the open state changes. Not called on initial render.

Controller

MemberTypeDescription
open()() => voidOpen the collapsible.
close()() => voidClose the collapsible.
toggle()() => voidToggle the collapsible.
isOpenboolean (getter)Current open state.
destroy()() => voidRemove all listeners, observers, and clean up.

Controller

The controller is returned by Collapsible.createCollapsible() and provides imperative control over the collapsible's state.

const controller = Collapsible.createCollapsible(root);

// Open programmatically
controller.open();

// Close programmatically
controller.close();

// Toggle
controller.toggle();

// Read current state
console.log(controller.isOpen); // true | false

// Clean up
controller.destroy();