Melt UI provides headless, accessible component builders for Svelte that follow WAI-ARIA guidelines. The Collapsible builder allows you to create expandable/collapsible content sections with full keyboard navigation and screen reader support out of the box.
This guide walks through a practical, production-ready implementation of collapsible components using Melt UI's createCollapsible builder. The focus is on real usage patterns, not abstractions.
Why Use @melt-ui/svelte (Melt UI) with Svelte
Svelte applications often need expandable content sections for FAQs, accordions, navigation menus, and detail views. Melt UI's Collapsible builder fits naturally into this ecosystem because it provides:
- Full WAI-ARIA compliance for accessibility
- Headless design (you control all styling)
- TypeScript support with excellent type safety
- Simple, declarative API
- Built-in keyboard navigation
- No external dependencies beyond Svelte
The builder pattern allows you to attach collapsible behavior to any elements you want, giving you complete control over the markup and styles.
Requirements
Before starting, make sure you have:
- A Svelte project (SvelteKit or standalone Svelte)
- Node.js 16+ and npm/pnpm/yarn
- Basic familiarity with Svelte components and stores
Installation
Install Melt UI using your preferred package manager:
npm install @melt-ui/svelte
# or
pnpm add @melt-ui/svelte
# or
yarn add @melt-ui/svelte
The package includes all builders and their TypeScript definitions.
Configuration
No additional configuration is required. Melt UI works out of the box with Svelte. If you're using TypeScript, ensure your tsconfig.json includes proper module resolution:
{
"compilerOptions": {
"moduleResolution": "bundler",
"target": "ES2020"
}
}
Basic Usage
The simplest way to create a collapsible section is using the createCollapsible builder:
<script>
import { createCollapsible, melt } from '@melt-ui/svelte'
const {
elements: { root, content, trigger },
states: { open }
} = createCollapsible()
</script>
<div use:melt={$root}>
<button use:melt={$trigger}>
{$open ? 'Close' : 'Open'}
</button>
<div use:melt={$content}>
<p>This content can be expanded and collapsed.</p>
</div>
</div>
The createCollapsible() function returns:
-
elements: Svelte stores for root, content, and trigger elements -
states: Reactive stores likeopenthat track the collapsible state
Use the melt action to attach the builder's behavior to your elements. The open store is reactive and updates automatically when the collapsible state changes.
Advanced Features
Styling with Tailwind CSS
Since Melt UI is headless, you have complete control over styling:
<script>
import { createCollapsible, melt } from '@melt-ui/svelte'
const {
elements: { root, content, trigger },
states: { open }
} = createCollapsible()
</script>
<div
use:melt={$root}
class="border rounded-lg overflow-hidden"
>
<button
use:melt={$trigger}
class="w-full px-4 py-3 text-left font-medium bg-gray-100 hover:bg-gray-200 flex items-center justify-between transition-colors"
>
<span>Toggle Content</span>
<span class="transform transition-transform {$open ? 'rotate-180' : ''}">
▼
</span>
</button>
<div
use:melt={$content}
class="px-4 py-3 bg-white"
>
<p>This is the collapsible content area.</p>
</div>
</div>
Controlled State
To control the collapsible state externally, pass a writable store:
<script>
import { createCollapsible, melt } from '@melt-ui/svelte'
import { writable } from 'svelte/store'
const isOpen = writable(false)
const {
elements: { root, content, trigger },
states: { open }
} = createCollapsible({
open: isOpen
})
function toggle() {
isOpen.update(n => !n)
}
</script>
<div use:melt={$root}>
<button use:melt={$trigger} on:click={toggle}>
{$open ? 'Close' : 'Open'}
</button>
<div use:melt={$content}>
Controlled content
</div>
</div>
Creating an Accordion
Build an accordion by managing multiple collapsible states:
<script>
import { createCollapsible, melt } from '@melt-ui/svelte'
import { writable } from 'svelte/store'
const openIndex = writable<number | null>(null)
const items = [
{ id: 1, title: 'Item 1', content: 'Content for item 1' },
{ id: 2, title: 'Item 2', content: 'Content for item 2' },
{ id: 3, title: 'Item 3', content: 'Content for item 3' }
]
function createItemCollapsible(index: number) {
const isOpen = writable(openIndex.get() === index)
const unsubscribe = openIndex.subscribe(value => {
isOpen.set(value === index)
})
return {
collapsible: createCollapsible({ open: isOpen }),
unsubscribe
}
}
</script>
<div class="space-y-2">
{#each items as item, index}
{@const { collapsible, unsubscribe } = createItemCollapsible(index)}
{@const { elements: { root, content, trigger }, states: { open } } = collapsible}
<div use:melt={$root} class="border rounded">
<button
use:melt={$trigger}
on:click={() => openIndex.set($open ? null : index)}
class="w-full px-4 py-2 text-left"
>
{item.title}
</button>
<div use:melt={$content}>
<div class="px-4 py-2">{item.content}</div>
</div>
</div>
{/each}
</div>
Custom Animations
Add animations using Svelte's transition directives:
<script>
import { createCollapsible, melt } from '@melt-ui/svelte'
import { slide } from 'svelte/transition'
const {
elements: { root, content, trigger },
states: { open }
} = createCollapsible()
</script>
<div use:melt={$root}>
<button use:melt={$trigger}>Toggle</button>
{#if $open}
<div use:melt={$content} transition:slide>
<p>This content slides in and out.</p>
</div>
{/if}
</div>
Common Problems / Troubleshooting
Collapsible doesn't toggle
- Ensure
meltaction is applied to all three elements (root, trigger, content) - Check that stores are accessed with
$prefix:$root,$trigger,$content - Verify the builder is called in component initialization, not conditionally
Accessibility attributes missing
- Make sure
meltaction is applied to elements, not removed by conditional rendering - Don't manually add ARIA attributes - the builder handles them automatically
TypeScript errors
- Import types from
@melt-ui/svelteif needed - Ensure Svelte version is 4+ for best TypeScript support
Content not animating
- When using conditional rendering with
{#if}, ensure themeltaction is still applied - Use Svelte transitions on the content element, not the root
Production Best Practices
- Always use the
meltaction on all builder elements to ensure accessibility - Test with keyboard navigation (Tab, Enter, Space, Escape)
- Use screen readers to verify ARIA attributes work correctly
- Keep styling separate from logic - Melt UI handles behavior, you handle appearance
- Consider using CSS transitions instead of JavaScript animations for better performance
- If building an accordion, ensure only one item is open at a time for better UX
- Clean up subscriptions when components are destroyed to prevent memory leaks
Final Notes
Melt UI's Collapsible builder provides a solid foundation for expandable content with minimal setup. The headless approach gives you complete control over styling while ensuring accessibility compliance.
Once you have collapsibles working, common next steps include:
- Building accordion components with shared state
- Adding custom animations and transitions
- Integrating with routing for expandable navigation menus
- Creating detail views with collapsible sections
Top comments (0)