Collapsible
An expandable/collapsible content panel built on bits-ui. The trigger snippet receives props that must be spread onto your Button or element.
Playground
Experiment with different props in real-time.
This content toggles with a smooth slide animation.
Basic Usage
Spread props onto Button to make it the toggle trigger.
This is the collapsible content. It can contain any elements — text, forms, images, or other components.
<script lang="ts">
import { Collapsible, Button } from 'sv5ui';
</script>
<Collapsible>
{#snippet trigger({ open, props })}
<Button {...props} variant="outline" label={open ? 'Hide' : 'Show more'}
trailingIcon={open ? 'lucide:chevron-up' : 'lucide:chevron-down'} />
{/snippet}
{#snippet content()}
<div class="mt-3 rounded-lg border border-on-surface/15 p-4">
<p class="text-sm text-on-surface/70">
This is the collapsible content. It can contain any elements — text, forms, images, or other components.
</p>
</div>
{/snippet}
</Collapsible>Controlled
Use bind:open for external control — no trigger snippet needed.
State: open
Controlled externally via bind:open.
<script lang="ts">
import { Collapsible, Button } from 'sv5ui';
let open = $state(true);
</script>
<div class="flex items-center gap-3">
<Button label={open ? 'Collapse' : 'Expand'} size="sm"
onclick={() => open = !open} />
<p class="text-sm text-on-surface/50">State: {open ? 'open' : 'closed'}</p>
</div>
<Collapsible bind:open>
{#snippet content()}
<div class="mt-3 rounded-lg border border-on-surface/15 p-4">
<p class="text-sm">Controlled externally via bind:open — no trigger snippet needed.</p>
</div>
{/snippet}
</Collapsible>Disabled
Prevent toggling with the disabled prop.
This content cannot be toggled.
<Collapsible disabled>
{#snippet trigger({ open, props })}
<Button {...props} variant="outline" label="Disabled" disabled
trailingIcon="lucide:chevron-down" />
{/snippet}
{#snippet content()}
<p class="mt-3 text-sm">This content cannot be toggled.</p>
{/snippet}
</Collapsible>FAQ Example
Build an accordion-style FAQ with Collapsible.
<script lang="ts">
import { Collapsible, Icon } from 'sv5ui';
const faqs = [
{ q: 'What is sv5ui?', a: 'A modern Svelte 5 UI component library built with Tailwind CSS 4.' },
{ q: 'Is it free?', a: 'Yes, sv5ui is fully open source under the MIT license.' },
{ q: 'How do I install it?', a: 'Run npm install sv5ui or bun add sv5ui in your project.' }
];
</script>
<div class="divide-y divide-on-surface/10 rounded-xl border border-on-surface/15">
{#each faqs as faq (faq.q)}
<Collapsible>
{#snippet trigger({ open, props })}
<button {...props} class="flex w-full items-center justify-between px-5 py-4 text-left text-sm font-medium hover:bg-surface-container/30 transition-colors">
{faq.q}
<Icon name="lucide:chevron-down" size={16}
class="shrink-0 text-on-surface/40 transition-transform duration-200 {open ? 'rotate-180' : ''}" />
</button>
{/snippet}
{#snippet content()}
<div class="px-5 pb-4 text-sm leading-relaxed text-on-surface/60">
{faq.a}
</div>
{/snippet}
</Collapsible>
{/each}
</div>Details Panel
Expand to reveal additional information.
Version: 1.4.0
License: MIT
Author: NDLab
Repository: github.com/ndlabdev/sv5ui
<Collapsible>
{#snippet trigger({ open, props })}
<Button {...props} variant="soft" size="sm"
label={open ? 'Less details' : 'More details'}
leadingIcon={open ? 'lucide:minus' : 'lucide:plus'} />
{/snippet}
{#snippet content()}
<div class="mt-3 space-y-2 rounded-lg bg-surface-container p-4 text-sm">
<p><strong>Version:</strong> 1.4.0</p>
<p><strong>License:</strong> MIT</p>
<p><strong>Author:</strong> NDLab</p>
<p><strong>Repository:</strong> github.com/ndlabdev/sv5ui</p>
</div>
{/snippet}
</Collapsible>Custom Trigger
Spread props onto any element for a fully custom trigger.
<script lang="ts">
import { Collapsible, Icon } from 'sv5ui';
</script>
<Collapsible>
{#snippet trigger({ open, props })}
<div {...props} class="flex cursor-pointer items-center gap-3 rounded-lg border border-on-surface/15 p-3 transition-colors hover:bg-surface-container/50">
<Icon name={open ? 'lucide:folder-open' : 'lucide:folder'} class="text-primary" />
<span class="text-sm font-medium flex-1">Project Files</span>
<Icon name="lucide:chevron-right" size={16}
class="transition-transform duration-200 {open ? 'rotate-90' : ''}" />
</div>
{/snippet}
{#snippet content()}
<div class="ml-5 mt-1 space-y-1.5 border-l-2 border-on-surface/10 pl-4 py-2">
<div class="flex items-center gap-2 text-sm text-on-surface/60">
<Icon name="lucide:file" size={14} /> index.ts
</div>
<div class="flex items-center gap-2 text-sm text-on-surface/60">
<Icon name="lucide:file" size={14} /> App.svelte
</div>
<div class="flex items-center gap-2 text-sm text-on-surface/60">
<Icon name="lucide:file" size={14} /> styles.css
</div>
</div>
{/snippet}
</Collapsible>UI Slots
Use the ui prop to override classes on internal elements.
| Slot | Description |
|---|---|
root | Outermost wrapper — controls layout and disabled state |
content | Content container — controls collapse animation |
Snippets
The trigger snippet receives props — spread them onto Button or any element.
| Snippet | Description |
|---|---|
trigger | Toggle element — receives { open, props }. Spread props onto Button or element. |
content | Content displayed when expanded |
children | Full custom layout using bits-ui Collapsible primitives directly |
Props
| Prop | Type | Default |
|---|---|---|
open | boolean | false |
onOpenChange | (open) => void | - |
onOpenChangeComplete | () => void | - |
disabled | boolean | false |
ref | HTMLElement | null | null |
class | string | - |
ui | Record<Slot, Class> | - |