Collapsible
Expand and collapse a region of content.
@ieedan starred 3 packages
import { Div, H4, Span } from "@implementjs/core";
import { ChevronsUpDownIcon } from "@implementjs/lucide";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/lib/components/ui/collapsible";
function Repo(name: string) {
return Div({ class: "rounded-md border px-4 py-2 font-mono text-sm" }, name);
}
export default function CollapsibleDemo() {
return Collapsible(
{ class: "w-full max-w-md" },
Div(
{ class: "flex items-center justify-between gap-2 px-4" },
H4({ class: "text-sm font-semibold" }, "@ieedan starred 3 packages"),
CollapsibleTrigger(
{ size: "icon-sm" },
ChevronsUpDownIcon({ "aria-hidden": true, class: "size-4" }),
Span({ class: "sr-only" }, "Toggle"),
),
),
Repo("@implementjs/core"),
CollapsibleContent(
{ class: "flex flex-col gap-2" },
Repo("@implementjs/primitives"),
Repo("@implementjs/lucide"),
),
);
}A collapsible is a single region that opens and closes. Collapsible is the root, CollapsibleTrigger is the control that toggles it, and CollapsibleContent is the body. Use it when one piece of UI should hide and show. For a stack of sections, use Accordion.
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@implementjs/primitives";
Collapsible(
CollapsibleTrigger("Show more"),
CollapsibleContent("The rest of the details live here."),
);
Each part accepts optional props and children — pass a props object when you need attributes, or pass children directly. See createComponent. Extra props are forwarded onto the underlying Div or Button.
Open state
Collapsible owns whether the body is open. Pass a boolean to seed it, or a signal to control it from outside (signal() returns a writable unchanged, so the same prop accepts both):
const open = signal(false);
Collapsible(
{ open },
CollapsibleTrigger("Show more"),
CollapsibleContent("The rest of the details live here."),
);
Button({ onClick: () => open.set(true) }, "Expand");
The trigger and the content
CollapsibleTrigger renders a Button. Clicking it toggles the region. CollapsibleContent is a Div that sets the hidden attribute while closed, so the body is out of the accessibility tree and not shown.
If you want find-in-page to still search closed content, pass hiddenUntilFound. Closed content then uses hidden="until-found" instead of the boolean hidden attribute, and the browser can reveal a match.
CollapsibleContent({ hiddenUntilFound: true }, LongAnswer());
The trigger sets aria-expanded from the open state and aria-controls to the content's id, so assistive technology can associate the two.
Styling
Every part sets a data-collapsible-* attribute so you can target it in CSS, and the root, trigger, and content expose data-state as "open" or "closed":
CollapsibleTrigger({ class: "inline-flex items-center gap-2 text-sm font-medium" }, "Show more");
CollapsibleContent({ class: "text-sm text-foreground/70" }, "The rest of the details live here.");
The hidden attribute already hides closed content. data-state is there for transitions, chevrons, and anything else that should react to open versus closed without you threading a signal through.
Animating open and close
CollapsibleContent measures itself whenever it opens or closes and exposes the result as --ip-collapsible-content-height and --ip-collapsible-content-width on the element, so keyframes can animate between zero and the natural size. When the region closes, the content keeps rendering until any animation running on it finishes — only then does the hidden attribute go on.
[data-collapsible-content] {
overflow: hidden;
}
[data-collapsible-content][data-state="open"] {
animation: collapsible-down 0.2s ease-out;
}
[data-collapsible-content][data-state="closed"] {
animation: collapsible-up 0.2s ease-out;
}
@keyframes collapsible-down {
from {
height: 0;
}
to {
height: var(--ip-collapsible-content-height);
}
}
@keyframes collapsible-up {
from {
height: var(--ip-collapsible-content-height);
}
to {
height: 0;
}
}
Content that is open on first render does not replay its open animation on page load.
API Reference
Collapsible
The root. Owns whether the content is open and provides that to the parts inside it. Renders a Div; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
open | Signal<boolean> | boolean | false | The open state. Pass a signal to control it from outside; a boolean seeds uncontrolled state. |
| Data attribute | Value |
|---|---|
[data-collapsible-root] | Present |
[data-state] | "open" | "closed" |
CollapsibleTrigger
Toggles the content open and closed. Renders a Button; extra props are forwarded onto it.
| Data attribute | Value |
|---|---|
[data-collapsible-trigger] | Present |
[data-state] | "open" | "closed" |
CollapsibleContent
The body. Hidden with the `hidden` attribute while closed; a close animation on it finishes before the attribute is set. Renders a Div; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
hiddenUntilFound | boolean | false | Closed content uses hidden="until-found" so find-in-page can reveal it. |
| Data attribute | Value |
|---|---|
[data-collapsible-content] | Present |
[data-state] | "open" | "closed" |
| CSS variable | Description |
|---|---|
--ip-collapsible-content-height | The natural height of the content, for open/close animations. |
--ip-collapsible-content-width | The natural width of the content, for open/close animations. |