Overlay

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.

Basic Usage

Spread props onto Button to make it the toggle trigger.

<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.

<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.

<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.

SlotDescription
rootOutermost wrapper — controls layout and disabled state
contentContent container — controls collapse animation

Snippets

The trigger snippet receives props — spread them onto Button or any element.

SnippetDescription
triggerToggle element — receives { open, props }. Spread props onto Button or element.
contentContent displayed when expanded
childrenFull custom layout using bits-ui Collapsible primitives directly

Props

PropTypeDefault
openbooleanfalse
onOpenChange(open) => void-
onOpenChangeComplete() => void-
disabledbooleanfalse
refHTMLElement | nullnull
classstring-
uiRecord<Slot, Class>-