Collapsible

Conceals or reveals content sections, enhancing space utilization and organization.

@huntabyte starred 3 repositories

	<script lang="ts">
  import { Collapsible } from "bits-ui";
  import CaretUpDown from "phosphor-svelte/lib/CaretUpDown";
</script>
 
<Collapsible.Root class="w-[327px] space-y-3">
  <div class="flex items-center justify-between space-x-10">
    <h4 class="text-[15px] font-medium">@huntabyte starred 3 repositories</h4>
    <Collapsible.Trigger
      class="inline-flex h-10 w-10 items-center justify-center rounded-9px border border-border-input bg-background-alt text-foreground shadow-btn transition-all hover:bg-muted active:scale-98"
    >
      <CaretUpDown class="size-4 " weight="bold" />
      <span class="sr-only">Toggle</span>
    </Collapsible.Trigger>
  </div>
 
  <Collapsible.Content
    class="space-y-2 font-mono text-[15px] tracking-[0.01em]"
  >
    <div
      class="inline-flex h-12 w-full items-center rounded-9px bg-muted px-[18px] py-3"
    >
      @huntabyte/bits-ui
    </div>
    <div
      class="inline-flex h-12 w-full items-center rounded-9px bg-muted px-[18px] py-3"
    >
      @huntabyte/shadcn-svelte
    </div>
    <div
      class="inline-flex h-12 w-full items-center rounded-9px bg-muted px-[18px] py-3"
    >
      @melt-ui/melt-ui
    </div>
  </Collapsible.Content>
</Collapsible.Root>

Overview

The Collapsible component enables you to create expandable and collapsible content sections. It provides an efficient way to manage space and organize information in user interfaces, enabling users to show or hide content as needed.

Key Features

  • Accessibility: ARIA attributes for screen reader compatibility and keyboard navigation.
  • Transition Support: CSS variables and data attributes for smooth transitions between states.
  • Flexible State Management: Supports controlled and uncontrolled state, take control if needed.
  • Compound Component Structure: Provides a set of subcomponents that work together to create a fully-featured collapsible.

Architecture

The Accordion component is composed of a few subcomponents, each with a specific role:

  • Root: The parent container that manages the state and context for the collapsible functionality.
  • Trigger: The interactive element (e.g., button) that toggles the expanded/collapsed state of the content.
  • Content: The container for the content that will be shown or hidden based on the collapsible state.

Structure

Here's an overview of how the Collapsible component is structured in code:

	<script lang="ts">
	import { Collapsible } from "bits-ui";
</script>
 
<Collapsible.Root>
	<Collapsible.Trigger />
	<Collapsible.Content />
</Collapsible.Root>

Reusable Components

It's recommended to use the Collapsible primitives to create your own custom collapsible component that can be used throughout your application.

MyCollapsible.svelte
	<script lang="ts">
	import { Collapsible, type WithoutChild } from "bits-ui";
 
	type Props = WithoutChild<Collapsible.RootProps> & {
		buttonText: string;
	};
 
	let {
		open = $bindable(false),
		ref = $bindable(null),
		buttonText,
		children,
		...restProps
	}: Props = $props();
</script>
 
<Collapsible.Root bind:open bind:ref {...restProps}>
	<Collapsible.Trigger>{buttonText}</Collapsible.Trigger>
	<Collapsible.Content>
		{@render children?.()}
	</Collapsible.Content>
</Collapsible.Root>

You can then use the MyCollapsible component in your application like so:

+page.svelte
	<script lang="ts">
	import MyCollapsible from "$lib/components/MyCollapsible.svelte";
</script>
 
<MyCollapsible buttonText="Open Collapsible">Here is my collapsible content.</MyCollapsible>

Managing Open State

Bits UI offers several approaches to manage and synchronize the Collapsible's open state, catering to different levels of control and integration needs.

1. Two-Way Binding

For seamless state synchronization, use Svelte's bind:open directive. This method automatically keeps your local state in sync with the Collapsible's internal state.

	<script lang="ts">
	import { Collapsible } from "bits-ui";
	let isOpen = $state(false);
</script>
 
<button onclick={() => (isOpen = true)}>Open Collapsible</button>
 
<Collapsible.Root bind:open={isOpen}>
	<!-- ... -->
</Collapsible.Root>

Key Benefits

  • Simplifies state management
  • Automatically updates isOpen when the collapsible closes (e.g., via trigger press)
  • Allows external control (e.g., opening via a separate button)

2. Change Handler

For more granular control or to perform additional logic on state changes, use the onOpenChange prop. This approach is useful when you need to execute custom logic alongside state updates.

	<script lang="ts">
	import { Collapsible } from "bits-ui";
	let isOpen = $state(false);
</script>
 
<Collapsible.Root
	open={isOpen}
	onOpenChange={(open) => {
		isOpen = open;
		// additional logic here.
	}}
>
	<!-- ... -->
</Collapsible.Root>

Use Cases

  • Implementing custom behaviors on open/close
  • Integrating with external state management solutions
  • Triggering side effects (e.g., logging, data fetching)

3. Fully Controlled

For complete control over the Collapsible's open state, use the controlledOpen prop. This approach requires you to manually manage the open state, giving you full control over when and how the collapsible responds to open/close events.

To implement controlled state:

  1. Set the controlledOpen prop to true on the Collapsible.Root component.
  2. Provide an open prop to Collapsible.Root, which should be a variable holding the current state.
  3. Implement an onOpenChange handler to update the state when the internal state changes.
	<script lang="ts">
	import { Collapsible } from "bits-ui";
 
	let myOpen = $state(false);
</script>
 
<Collapsible.Root controlledOpen open={myOpen} onOpenChange={(o) => (myOpen = o)}>
	<!-- ... -->
</Collapsible.Root>

When to Use

  • Implementing complex open/close logic
  • Coordinating multiple UI elements
  • Debugging state-related issues

Svelte Transitions

The Collapsible component can be enhanced with Svelte's built-in transition effects or other animation libraries.

Using forceMount and child Snippets

To apply Svelte transitions to Collapsible components, use the forceMount prop in combination with the child snippet. This approach gives you full control over the mounting behavior and animation of the Collapsible.Content.

	<script lang="ts">
	import { Collapsible } from "bits-ui";
	import { fade } from "svelte/transition";
</script>
 
<Collapsible.Root>
	<Collapsible.Trigger>Open</Collapsible.Trigger>
	<Collapsible.Content forceMount>
		{#snippet child({ props, open })}
			{#if open}
				<div {...props} transition:fade>
					<!-- ... -->
				</div>
			{/if}
		{/snippet}
	</Collapsible.Content>
</Collapsible.Root>

In this example:

  • The forceMount prop ensures the content is always in the DOM.
  • The child snippet provides access to the open state and component props.
  • Svelte's #if block controls when the content is visible.
  • Transition directive (transition:fade) apply the animations.

Best Practices

For cleaner code and better maintainability, consider creating custom reusable components that encapsulate this transition logic.

MyCollapsibleContent.svelte
	<script lang="ts">
	import { Collapsible, type WithoutChildrenOrChild } from "bits-ui";
	import { fade } from "svelte/transition";
	import type { Snippet } from "svelte";
 
	let {
		ref = $bindable(null),
		duration = 200,
		children,
		...restProps
	}: WithoutChildrenOrChild<Collapsible.ContentProps> & {
		duration?: number;
		children?: Snippet;
	} = $props();
</script>
 
<Collapsible.Content forceMount bind:ref {...restProps}>
	{#snippet child({ props, open })}
		{#if open}
			<div {...props} transition:fade={{ duration }}>
				{@render children?.()}
			</div>
		{/if}
	{/snippet}
</Collapsible.Content>

You can then use the MyCollapsibleContent component alongside the other Collapsible primitives throughout your application:

	<script lang="ts">
	import { Collapsible } from "bits-ui";
	import { MyCollapsibleContent } from "$lib/components";
</script>
 
<Collapsible.Root>
	<Collapsible.Trigger>Open</Collapsible.Trigger>
	<MyCollapsibleContent duration={300}>
		<!-- ... -->
	</MyCollapsibleContent>
</Collapsible.Root>

API Reference

Collapsible.Root

The root collapsible container which manages the state of the collapsible.

Property Type Description
open $bindable
boolean

The open state of the collapsible. The content will be visible when this is true, and hidden when it's false.

Default: false
onOpenChange
function

A callback that is fired when the collapsible's open state changes.

Default: undefined
controlledOpen
boolean

Whether or not the open state is controlled or not. If true, the component will not update the open state internally, instead it will call onOpenChange when it would have otherwise, and it is up to you to update the open prop that is passed to the component.

Default: false
disabled
boolean

Whether or not the collapsible is disabled. This prevents the user from interacting with it.

Default: false
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-state
enum

The collapsible's open state.

data-disabled
''

Present when the collapsible is disabled.

data-collapsible-root
''

Present on the root element.

Collapsible.Trigger

The button responsible for toggling the collapsible's open state.

Property Type Description
ref $bindable
HTMLButtonElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-state
enum

The collapsible's open state.

data-disabled
''

Present when the collapsible or this trigger is disabled.

data-collapsible-trigger
''

Present on the trigger element.

Collapsible.Content

The content displayed when the collapsible is open.

Property Type Description
forceMount
boolean

Whether or not to forcefully mount the content. This is useful if you want to use Svelte transitions or another animation library for the content.

Default: false
ref $bindable
HTMLDivElement

The underlying DOM element being rendered. You can bind to this to get a reference to the element.

Default: undefined
children
Snippet

The children content to render.

Default: undefined
child
Snippet

Use render delegation to render your own element. See delegation docs for more information.

Default: undefined
Data Attribute Value Description
data-state
enum

The collapsible's open state.

data-disabled
''

Present when the collapsible is disabled.

data-collapsible-content
''

Present on the content element.

CSS Variable Description
--bits-collapsible-content-height

The height of the collapsible content element.

--bits-collapsible-content-width

The width of the collapsible content element.