Collapsible
collapsibleuiHeadless single-region show/hide primitive with Svelte 5 runes (no headless dependency — open state, ids, and trigger/content wiring are hand-rolled via context). Use it when Accordion is overkill — a single toggle reveals one panel of content. Controlled (bind:open) or uncontrolled (defaultOpen); the trigger supports a child snippet for custom buttons.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/collapsible.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/collapsible.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/collapsible.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/collapsible.jsonManifest:
https://uipkge.dev/r/svelte/collapsible.jsonInstalls to:src/lib/components/ui/collapsible/Examples
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
openControlled open state. Bindable (`bind:open`); falls back to internal state seeded by `defaultOpen`. | boolean | — | optional |
defaultOpenInitial open state when `open` is unbound. | boolean | false | optional |
disabledWhen `true`, the trigger cannot toggle the content. | boolean | false | optional |
onOpenChangeFires when the open state changes. | (open: boolean) => void | — | optional |
children | Snippet<[{ open: boolean }]> | — | optional |
Used by
Files installed (4)
src/lib/components/ui/collapsible/Collapsible.svelte2 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface CollapsibleProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> { /** Controlled open state. Bindable (`bind:open`); falls back to internal state seeded by `defaultOpen`. */ open?: boolean /** Initial open state when `open` is unbound. */ defaultOpen?: boolean /** When `true`, the trigger cannot toggle the content. */ disabled?: boolean /** Fires when the open state changes. */ onOpenChange?: (open: boolean) => void children?: Snippet<[{ open: boolean }]> } /** Hand-rolled collapsible context (replaces reka-ui's CollapsibleRoot state). */ export interface CollapsibleContext { readonly isOpen: boolean readonly disabled: boolean readonly contentId: string toggle: () => void } </script> <script lang="ts"> import { setContext } from 'svelte' let { class: className, open = $bindable(), defaultOpen = false, disabled = false, onOpenChange, children, ...restProps }: CollapsibleProps = $props() // Intentional one-shot seed: later `defaultOpen` changes must not clobber user toggles. // svelte-ignore state_referenced_locally let internalOpen = $state(defaultOpen) const isOpen = $derived(open ?? internalOpen) const contentId = `collapsible-${Math.random().toString(36).slice(2, 9)}` function toggle() { if (disabled) return const next = !isOpen if (open === undefined) internalOpen = next open = next onOpenChange?.(next) } setContext<CollapsibleContext>('collapsibleContext', { get isOpen() { return isOpen }, get disabled() { return disabled }, get contentId() { return contentId }, toggle, }) </script> <div data-uipkge data-slot="collapsible" data-state={isOpen ? 'open' : 'closed'} class={className} {...restProps}> {@render children?.({ open: isOpen })} </div>src/lib/components/ui/collapsible/CollapsibleContent.svelte0.9 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface CollapsibleContentProps extends HTMLAttributes<HTMLDivElement> { /** Keep the content mounted (hidden) when closed instead of unmounting it. */ forceMount?: boolean children?: Snippet } </script> <script lang="ts"> import { getContext } from 'svelte' import type { CollapsibleContext } from './Collapsible.svelte' let { forceMount = false, children, ...restProps }: CollapsibleContentProps = $props() const ctx = getContext<CollapsibleContext | null>('collapsibleContext') const isOpen = $derived(ctx?.isOpen ?? false) </script> {#if forceMount || isOpen} <div data-uipkge data-slot="collapsible-content" id={ctx?.contentId} data-state={isOpen ? 'open' : 'closed'} hidden={!isOpen || undefined} {...restProps} > {@render children?.()} </div> {/if}src/lib/components/ui/collapsible/CollapsibleTrigger.svelte1.8 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface CollapsibleTriggerProps extends HTMLButtonAttributes { /** * Render your own element with the trigger's props instead of emitting a * `<button>` — the Svelte counterpart of React's `asChild`. Spread `props` * onto your element. */ child?: Snippet<[{ props: Record<string, unknown> }]> /** The rendered `<button>`, via `bind:ref`. Stays null in `child` mode. */ ref?: HTMLButtonElement | null children?: Snippet } </script> <script lang="ts"> import { getContext } from 'svelte' import type { MouseEventHandler } from 'svelte/elements' import type { CollapsibleContext } from './Collapsible.svelte' let { child, ref = $bindable(null), children, onclick: onclickProp, ...restProps }: CollapsibleTriggerProps = $props() const ctx = getContext<CollapsibleContext | null>('collapsibleContext') const handleClick: MouseEventHandler<HTMLButtonElement> = (event) => { ctx?.toggle() onclickProp?.(event) } // `onclick` is chained (toggle + consumer handler) so `child` buttons work // with a bare `{...props}` spread. It is safe to list before `...restProps` // — the consumer's own `onclick` was destructured out as `onclickProp`. const mergedProps = $derived({ 'data-uipkge': '', 'data-slot': 'collapsible-trigger', 'data-state': ctx?.isOpen ? 'open' : 'closed', 'aria-expanded': ctx?.isOpen ?? false, 'aria-controls': ctx?.contentId, disabled: ctx?.disabled, onclick: handleClick, ...restProps, }) </script> {#if child} {@render child({ props: mergedProps })} {:else} <button bind:this={ref} type="button" {...mergedProps}> {@render children?.()} </button> {/if}src/lib/components/ui/collapsible/index.ts0.3 kB
export { default as Collapsible, type CollapsibleContext, type CollapsibleProps } from './Collapsible.svelte' export { default as CollapsibleContent, type CollapsibleContentProps } from './CollapsibleContent.svelte' export { default as CollapsibleTrigger, type CollapsibleTriggerProps } from './CollapsibleTrigger.svelte'
Raw manifest:https://uipkge.dev/r/svelte/collapsible.json