Collapsible
A single expand/collapse panel with smooth animation, a default or custom trigger, and full ARIA support. The low-level primitive behind Accordion — use it standalone for simple show/hide patterns or as a building block for compound components.
Playground
Design tokens are named values — colors, spacing, radii — that form the single source of truth for your design system. They bridge the gap between design tools and code.
<Collapsible>
<p class="text-text-secondary text-sm">
Design tokens are named values — colors, spacing, radii — that form the single source of
truth for your design system. They bridge the gap between design tools and code.
</p>
</Collapsible>01 Examples
FAQ item
Named values — colors, spacing, radii — that form the single source of truth for your design system. They bridge the gap between design tools and code.
No. Every component ships with sensible defaults. Tailwind helps when you want to
override styles via slotClasses, but it's optional.
Yes. The components are SSR-safe and hydrate without layout shift. See the SvelteKit adapter docs for setup details.
<div class="flex w-full max-w-lg flex-col gap-3">
<Collapsible variant="card" title="What are design tokens?" defaultOpen>
<p class="text-text-secondary text-sm leading-relaxed">
Named values — colors, spacing, radii — that form the single source of truth for your
design system. They bridge the gap between design tools and code.
</p>
</Collapsible>
<Collapsible variant="card" title="Do I need to learn Tailwind?">
<p class="text-text-secondary text-sm leading-relaxed">
No. Every component ships with sensible defaults. Tailwind helps when you want to
override styles via <code class="bg-surface-base rounded px-1.5 py-0.5 text-xs"
>slotClasses</code
>, but it's optional.
</p>
</Collapsible>
<Collapsible variant="card" title="Can I use this with SvelteKit?">
<p class="text-text-secondary text-sm leading-relaxed">
Yes. The components are SSR-safe and hydrate without layout shift. See the SvelteKit
adapter docs for setup details.
</p>
</Collapsible>
</div>Controlled section toggle
This panel is controlled via bind:open. Toggle it with the button above or by clicking the trigger.
<div class="flex w-full max-w-lg flex-col gap-4">
<div class="flex items-center gap-3">
<Button size="sm" variant="outlined" onclick={() => (controlledOpen = !controlledOpen)}>
{controlledOpen ? 'Hide filters' : 'Show filters'}
</Button>
<Badge size="xs" intent={controlledOpen ? 'success' : 'neutral'} variant="soft">
{controlledOpen ? 'open' : 'closed'}
</Badge>
</div>
<Collapsible variant="card" bind:open={controlledOpen} title="Advanced filters">
<p class="text-text-secondary text-sm">
This panel is controlled via <code class="bg-surface-base rounded px-1.5 py-0.5 text-xs"
>bind:open</code
>. Toggle it with the button above or by clicking the trigger.
</p>
</Collapsible>
</div>Release-notes item with custom trigger
Features: New Collapsible component, improved Accordion internals, Stepper navigation.
Fixes: Dialog focus trap on Safari, Tooltip positioning near edges.
<div class="w-full max-w-lg">
<Collapsible variant="card" defaultOpen>
{#snippet trigger({
open,
toggle
}: {
open: boolean;
toggle: () => void;
disabled: boolean;
triggerId: string;
contentId: string;
})}
<button
onclick={toggle}
class="hover:bg-surface-hover flex w-full items-center gap-3 px-4 py-3 text-left transition-colors"
>
<div
class="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg"
>
<ClipboardListIcon size={16} />
</div>
<div class="flex-1">
<p class="text-text-primary text-sm font-semibold">Release Notes v3.2</p>
<p class="text-text-tertiary text-xs">3 new features, 2 bug fixes</p>
</div>
<Badge size="xs" intent={open ? 'primary' : 'neutral'} variant="soft">
{open ? 'Expanded' : 'Collapsed'}
</Badge>
</button>
{/snippet}
<div class="space-y-2 px-4 pb-4">
<p class="text-text-secondary text-sm">
<strong>Features:</strong> New Collapsible component, improved Accordion internals, Stepper
navigation.
</p>
<p class="text-text-secondary text-sm">
<strong>Fixes:</strong> Dialog focus trap on Safari, Tooltip positioning near edges.
</p>
</div>
</Collapsible>
</div>02 Customization
slotClasses Override
<div class="w-full max-w-lg">
<Collapsible
variant="card"
defaultOpen
title="Custom styled"
slotClasses={{
base: 'border-primary/20',
trigger: 'hover:text-primary font-semibold',
chevron: 'text-primary',
contentInner: 'text-text-secondary text-sm leading-relaxed'
}}
>
Override individual slots without touching the component source. The card gets a
primary-tinted border, the trigger uses a semibold font, and the chevron matches the
primary intent.
</Collapsible>
</div>Glassmorphism (unstyled)
<div class="w-full max-w-lg">
<Collapsible
unstyled
defaultOpen
title="Frosted Glass"
class="overflow-hidden rounded-xl"
slotClasses={{
trigger:
'flex w-full items-center justify-between rounded-t-xl bg-white/15 px-5 py-3.5 text-sm font-medium text-white backdrop-blur-md transition-colors hover:bg-white/20',
chevron: 'size-4 text-white/60 transition-transform duration-200',
contentInner: 'bg-white/10 px-5 py-4 text-sm text-white/80 backdrop-blur-sm'
}}
>
The component strips all defaults in unstyled mode. Every visual detail is hand-crafted
through class props — background, blur, text color, spacing, and border radius.
</Collapsible>
</div>Terminal (unstyled)
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
Process exited with code 0
<div class="w-full max-w-lg">
<Collapsible
unstyled
defaultOpen
title="$ cat /etc/config"
class="font-mono"
slotClasses={{
trigger:
'flex w-full items-center justify-between py-3 text-xs text-emerald-300 transition-colors hover:text-emerald-400',
chevron: 'size-3.5 text-emerald-400 transition-transform duration-200',
contentInner: 'pb-3 text-xs leading-relaxed text-emerald-200'
}}
>
<p>NODE_ENV=production</p>
<p>PORT=3000</p>
<p>LOG_LEVEL=info</p>
<p class="mt-2 text-emerald-300">Process exited with code 0</p>
</Collapsible>
</div>Panel skins that recur (glass, terminal) belong in a BlocksProvider preset (presets.Collapsible), applied per instance via preset — see Customization.
03 Accessibility
Built-in ARIA
The default trigger uses aria-expanded and aria-controls to link to the content panel. The content panel has role="region" with aria-labelledby pointing back to the trigger. The data-state attribute exposes open / closed for CSS-only styling.
Keyboard Navigation
Tab moves focus to the trigger. Enter / Space toggle the content. Focus rings use focus-visible: so they only appear on keyboard navigation.
Custom Triggers
When using the trigger snippet, the component passes triggerId and contentId so you can wire up aria-expanded and aria-controls yourself. The content region always
gets the correct aria-labelledby.
Reduced Motion
The expand/collapse animation uses CSS grid-template-rows transitions. When prefers-reduced-motion is enabled, transition durations
are reduced via the design token system.
04 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
children required | Snippet | — | Content to render inside the Collapsible component | |
class | string | — | Additional CSS classes to apply to the Collapsible component | |
defaultOpen | boolean | false | Initial open state for uncontrolled usage | |
disabled | boolean | false | Whether the Collapsible is disabled and non-interactive | |
name | string | — | Base name for generating ARIA IDs. Defaults to auto-generated. | |
onOpenChange | (open: boolean) => void | — | Callback fired once per trigger-driven open transition, after the state is
applied. Not fired for consumer writes via bind:open. | |
open | boolean | — | Whether the content is visible. Supports bind:open. Trigger-driven transitions
are applied optimistically: open is updated first, then onOpenChange reports
the change. When passing open without bind:, mirror every onOpenChange
back into your state — an ignored change leaves the component and your source
of truth diverged. To conditionally reject transitions, drive open from your
own state and toggle it from a custom trigger snippet instead of calling the
provided toggle. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ Collapsible: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent
and make the custom look reusable across the project. | |
size variant | lgmdsm | md | Controls the dimensions, padding, and text size of the Collapsible. Affects the component's physical footprint. Available options: lg, md, sm. | |
slotClasses | Partial<Record<CollapsibleSlots, string>> | — | Per-slot class overrides. Slots: base | trigger | chevron | content | contentInner | |
title | string | — | Trigger label text (used by the default trigger) | |
transitionDuration | number | — | Override the expand/collapse animation duration in milliseconds. Defaults to the
--blocks-collapse-duration token (the normal 250ms). Set globally via that CSS
custom property or per-instance here. Respects prefers-reduced-motion (near-instant). | |
transitionEasing | string | — | Override the expand/collapse easing as a CSS <easing-function> — e.g. 'ease-in-out',
'cubic-bezier(0.4,0,0.2,1)', or a token such as 'var(--blocks-ease-springy)'. Defaults
to the --blocks-collapse-easing token.
Note: unlike the overlay components (Dialog/Drawer), whose Svelte transitions take an easing
**function** (t: number) => number, Collapsible animates via CSS — so its easing is a CSS
string. Same intent, representation follows the transition mechanism. | |
trigger | Snippet<[
{
open: boolean;
toggle: () => void;
disabled: boolean;
triggerId: string;
contentId: string;
}
]> | — | Custom trigger snippet — receives open state, toggle fn, disabled flag, and ARIA IDs | |
unstyled | boolean | — | Remove default styles | |
variant variant | carddefaultghost | default | Controls the visual style and presentation of the Collapsible. Determines the component's visual treatment. Available options: card, default, ghost. | |
...CollapsibleVariants variant | VariantProps | — | Styling variants from CollapsibleVariants | |
...HTMLAttributes<HTMLDivElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children') |
05 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
CollapsibleProps | interface | props | 0 | Props interface for the Collapsible component | |
CollapsibleVariants | type | variant | 1 | — | |
CollapsibleSlots | type | variant | 0 | Slot names derived from the tv() config — single source of truth for slotClasses. |
06 Installation
Import
import { Collapsible } from '@urbicon-ui/blocks';