Collapsible
The collapsible expands and collapses a single panel of content from a trigger, like extra details behind a "show more". For a set of stacked panels where opening one closes the others, use an accordion.
<div class="flex w-full gap-3">
<span class="avatar">
<img
class="avatar-image"
src="https://images.unsplash.com/photo-1750390200282-bf7f669a9946?w=640&h=640&fit=facearea&facepad=3.2&auto=format&q=100"
alt="Leila Navarro"
/>
<span class="avatar-fallback">LN</span>
</span>
<div class="flex min-w-0 flex-col items-start gap-1">
<p class="text-sm">
<span class="font-medium">Leila Navarro</span>
<span class="text-muted-foreground">· 2h</span>
</p>
<p class="text-sm">
Just shipped the new onboarding flow. Huge thanks to everyone who helped
with the design reviews!
</p>
<button
id="collapsible-demo-trigger"
class="group btn btn-ghost btn-sm -ml-2.5 text-muted-foreground"
type="button"
>
<svg class="icon-start transition-transform group-aria-expanded:rotate-180" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
<span class="group-aria-expanded:hidden">Show 2 replies</span>
<span class="hidden group-aria-expanded:inline">Hide replies</span>
</button>
<div class="collapsible" data-sp-toggle="#collapsible-demo-trigger">
<div class="ml-[3.5px] flex flex-col gap-4 border-l pt-2 pl-4">
<div class="flex gap-3">
<span class="avatar">
<img
class="avatar-image"
src="https://images.unsplash.com/photo-1752486268240-0507bb1ebc7e?w=640&h=640&fit=facearea&facepad=2.6&auto=format&q=100"
alt="Camille Laurent"
/>
<span class="avatar-fallback">EW</span>
</span>
<div class="flex flex-col gap-1">
<p class="text-sm">
<span class="font-medium">Camille Laurent</span>
<span class="text-muted-foreground">· 1h</span>
</p>
<p class="text-sm">Congrats! The new flow feels so much faster.</p>
</div>
</div>
<div class="flex gap-3">
<span class="avatar">
<img
class="avatar-image"
src="https://images.unsplash.com/photo-1750390200293-92d5a788d3a2?w=640&h=640&fit=facearea&facepad=3&auto=format&q=100"
alt="Theo Marchetti"
/>
<span class="avatar-fallback">MC</span>
</span>
<div class="flex flex-col gap-1">
<p class="text-sm">
<span class="font-medium">Theo Marchetti</span>
<span class="text-muted-foreground">· 45m</span>
</p>
<p class="text-sm">Love the progress indicators. Great work!</p>
</div>
</div>
</div>
</div>
</div>
</div>Usage
Add the collapsible class to the panel and point it at its trigger with data-sp-toggle. The trigger's aria-expanded follows the state, which is the styling hook for rotating chevrons or swapping labels.
<!-- This is the trigger -->
<button id="my-trigger" class="btn btn-outline">Show more</button>
<!-- This is the panel -->
<div class="collapsible" data-sp-toggle="#my-trigger">
<div class="collapsible-content">Hidden until expanded.</div>
</div>Examples
Show more
Reveal the rest of a long text in place; the trigger swaps its label against the expanded state.
<div class="flex w-full flex-col items-start gap-2">
<h4 class="text-sm font-medium">About this project</h4>
<p class="text-sm text-muted-foreground">
Starting Point UI is an open-source component library for Tailwind CSS,
inspired by shadcn/ui. It gives you the same beautiful design system as
plain HTML classes, so it works in any project.
</p>
<div class="collapsible" data-sp-toggle="#collapsible-more-trigger">
<div class="flex flex-col gap-2 text-sm text-muted-foreground">
<p>
Components render correctly from markup and CSS before any JavaScript
loads, and the optional script layer adds behavior like this collapsible
on top.
</p>
<p>
Everything is themeable with CSS variables, and plain utility classes
always win over component styles, so you can override anything inline.
</p>
</div>
</div>
<button
id="collapsible-more-trigger"
class="group btn btn-link btn-sm px-0"
type="button"
>
<span class="group-aria-expanded:hidden">Show more</span>
<span class="hidden group-aria-expanded:inline">Show less</span>
</button>
</div>Initially open
Author the expanded class on the panel to start it open, and mirror aria-expanded="true" onto the trigger so state-driven styling, like a rotated chevron, paints correctly before JavaScript loads.
<nav class="sidebar-menu mx-auto w-full max-w-64">
<div class="sidebar-menu-item">
<button
id="collapsible-open-trigger"
class="group sidebar-menu-button"
type="button"
aria-expanded="true"
>
<svg class="transition-transform group-aria-expanded:rotate-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
<span>Documents</span>
</button>
<div
class="collapsible expanded"
data-sp-toggle="#collapsible-open-trigger"
>
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<button class="sidebar-menu-sub-button w-full" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>
<span>resume.pdf</span>
</button>
</li>
<li class="sidebar-menu-sub-item">
<button class="sidebar-menu-sub-button w-full" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>
<span>invoice.pdf</span>
</button>
</li>
<li class="sidebar-menu-sub-item">
<button class="sidebar-menu-sub-button w-full" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>
<span>notes.md</span>
</button>
</li>
</ul>
</div>
</div>
</nav>Nested
Collapsibles nest, so a file tree is triggers and panels all the way down. The sidebar menu classes give it a ready-made look, indent line included.
<nav class="sidebar-menu mx-auto w-full max-w-64">
<div class="sidebar-menu-item">
<button
id="ct-src"
class="group sidebar-menu-button"
type="button"
aria-expanded="true"
>
<svg class="transition-transform group-aria-expanded:rotate-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
<span>src</span>
</button>
<div class="collapsible expanded" data-sp-toggle="#ct-src">
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<button
id="ct-components"
class="group sidebar-menu-sub-button w-full"
type="button"
aria-expanded="true"
>
<svg class="transition-transform group-aria-expanded:rotate-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
<span>components</span>
</button>
<div class="collapsible expanded" data-sp-toggle="#ct-components">
<ul class="sidebar-menu-sub">
<li class="sidebar-menu-sub-item">
<button class="sidebar-menu-sub-button w-full" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>
<span>button.tsx</span>
</button>
</li>
<li class="sidebar-menu-sub-item">
<button class="sidebar-menu-sub-button w-full" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>
<span>card.tsx</span>
</button>
</li>
</ul>
</div>
</li>
<li class="sidebar-menu-sub-item">
<button class="sidebar-menu-sub-button w-full" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/></svg>
<span>index.ts</span>
</button>
</li>
</ul>
</div>
</div>
</nav>Options
Collapsible
Set each option with its own data-sp-* attribute on the collapsible element.
<div class="collapsible" data-sp-toggle="#my-trigger"></div>JavaScript
Events
Lifecycle events fire on the panel; before* events are cancelable.
// Veto collapsing, e.g. while a form inside has unsaved changes.
panel.addEventListener("sp-beforecollapse", (e) => {
if (unsavedChanges) e.preventDefault();
});Methods
Get the instance with sp.collapsible(el), then call its methods:
const collapsible = sp.collapsible(document.querySelector("#my-panel"));
collapsible.expand();