# Collapsible

> A single disclosure: a trigger that expands and collapses a region, animated without measuring so an interrupted toggle simply reverses, with the closed content kept in the page.

- Package: `fancy-ui-svelte` (Svelte 5 + Tailwind CSS v4)
- Category: Navigation
- Tags: collapsible, disclosure, expand, collapse, compound, navigation
- Types: `CollapsibleProps`, `CollapsibleTriggerProps`, `CollapsibleContentProps`, `CollapsibleContext`
- Docs: https://fancy-ui.rama.app/docs/components/collapsible
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/src/lib/fancy-ui/collapsible

## Import

```svelte
<script lang="ts">
  import { Collapsible } from 'fancy-ui-svelte';
</script>
```

Setup (once per project): `npm install fancy-ui-svelte`, then add `@import "fancy-ui-svelte/tailwind.css";` after `@import "tailwindcss";` in your main CSS file. Full guide: https://fancy-ui.rama.app/llms.txt

React: `import { Collapsible } from 'fancy-ui-react';` (`class` becomes `className`, snippets become `ReactNode`, DOM callbacks are camel-cased; exact names on https://fancy-ui.rama.app/docs/components/react/collapsible.md).

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | `false` | Whether the content is expanded, bindable. |
| `onOpenChange` | `(open: boolean) => void` | — | Called with the new state each time the trigger toggles it. |
| `disabled` | `boolean` | `false` | Makes the trigger inert. A bound open can still be changed from outside. |
| `class` | `string` | — | Additional CSS classes, merged onto the root. |
| `ref` | `HTMLDivElement \| null` | `null` | Bindable reference to the root element. |
| `CollapsibleTrigger.disabled` | `boolean` | `false` | Disables this trigger on top of the root's own disabled. |
| `CollapsibleTrigger.class` | `string` | — | Additional CSS classes, merged onto the trigger button. |
| `CollapsibleTrigger.ref` | `HTMLButtonElement \| null` | `null` | Bindable reference to the trigger's button element. |
| `CollapsibleContent.forceMount` | `boolean` | `true` | Keeps the children mounted while closed (inert and hidden). Pass false to unmount them once the collapse has finished and mount them again on open. |
| `CollapsibleContent.class` | `string` | — | Additional CSS classes, merged onto the inner body so padding collapses with the panel. |
| `CollapsibleContent.ref` | `HTMLDivElement \| null` | `null` | Bindable reference to the region element that animates. |

## Snippets

| Snippet | Description |
| --- | --- |
| `children` | A CollapsibleTrigger and a CollapsibleContent. |
| `CollapsibleTrigger.children` | The trigger's content. |
| `CollapsibleContent.children` | The content. |

## Examples

### Basic Usage

A trigger that reveals extra rows beneath a short list.

```svelte
<script lang="ts">
	import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "fancy-ui-svelte";

	let open = $state(false);
</script>

<Collapsible bind:open class="w-full max-w-sm">
	<div class="flex items-center justify-between gap-4">
		<h4 class="text-sm font-semibold">3 repositories starred</h4>
		<CollapsibleTrigger class="rounded-md border px-3 py-1 text-sm">
			{open ? "Show less" : "Show more"}
		</CollapsibleTrigger>
	</div>
	<div class="mt-2 rounded-md border px-3 py-2 font-mono text-sm">fancy-ui/core</div>
	<CollapsibleContent>
		<div class="mt-2 flex flex-col gap-2">
			<div class="rounded-md border px-3 py-2 font-mono text-sm">fancy-ui/motion</div>
			<div class="rounded-md border px-3 py-2 font-mono text-sm">fancy-ui/registry</div>
		</div>
	</CollapsibleContent>
</Collapsible>
```

### Multiple

Several independent panels, each with its own open state.

```svelte
<script lang="ts">
	import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "fancy-ui-svelte";

	const sections = [
		{ id: "a", title: "Appearance", body: "Theme, density and the accent colour." },
		{ id: "b", title: "Language", body: "The language of menus and messages." },
		{ id: "c", title: "Advanced", body: "Experimental options that may change." },
	];
</script>

<div class="flex w-full max-w-sm flex-col gap-2">
	{#each sections as section (section.id)}
		<Collapsible class="rounded-md border px-3" open={section.id === "a"}>
			<CollapsibleTrigger class="flex w-full items-center justify-between py-2 text-sm font-medium">
				{section.title}
			</CollapsibleTrigger>
			<CollapsibleContent class="text-muted-foreground pb-2 text-sm">
				{section.body}
			</CollapsibleContent>
		</Collapsible>
	{/each}
</div>
```

### Controlled

Open state shared with a button outside the component.

```svelte
<script lang="ts">
	import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "fancy-ui-svelte";
	import { Button } from "fancy-ui-svelte";

	let open = $state(false);
	let changes = $state(0);
</script>

<div class="flex w-full max-w-sm flex-col gap-3">
	<div class="flex flex-wrap items-center gap-2">
		<Button size="sm" variant="outline" onclick={() => (open = !open)}>Toggle from outside</Button>
		<span class="text-muted-foreground text-sm">
			{open ? "open" : "closed"} · {changes} change{changes === 1 ? "" : "s"} from the trigger
		</span>
	</div>

	<Collapsible bind:open onOpenChange={() => (changes += 1)} class="rounded-md border px-3">
		<CollapsibleTrigger class="flex w-full items-center justify-between py-2 text-sm font-medium">
			Release notes
		</CollapsibleTrigger>
		<CollapsibleContent class="text-muted-foreground pb-2 text-sm">
			Both the trigger and the button above drive the same state; only the trigger reports through
			onOpenChange.
		</CollapsibleContent>
	</Collapsible>
</div>
```
