Svelte 30: Component Composition Capstone — An Accordion Widget
easy⏱ 5 mincoursesvelte
Shared state via context, not props
Accordion.svelte holds openId as $state and exposes { isOpen, toggle } through setContext. Every AccordionItem calls getContext to reach that same shared object — they're siblings in the component tree, so passing this through props would mean Accordion manually forwarding callbacks to each child. Context sidesteps that entirely.
<!-- Accordion.svelte -->
<script>
import { setContext } from 'svelte';
let { children } = $props();
let openId = $state(null);
setContext('accordion', {
isOpen: (id) => openId === id,
toggle: (id) => openId = openId === id ? null : id
});
</script>
<div class='accordion'>{@render children()}</div>
Composable by construction
Notice App.svelte never mentions openId, isOpen, or toggle at all — it just nests AccordionItem tags inside Accordion and gives each one a title and some markup. All the coordination logic is encapsulated inside the two widget files, exactly like a real component library.
Allow multiple panels open at once
Change openId from a single value to a Set of open ids, and update isOpen/toggle in Accordion.svelte accordingly, so more than one item can stay expanded simultaneously.
Module recap
This capstone touches: components as files (21), snippets for reusable markup (22), children for content projection (23), and context for cross-tree communication (26), plus multi-file composition throughout. Named snippet props (24), dynamic components (25), class:/style: (27), actions (28), and svelte:window (29) are the remaining tools in your Svelte 5 composition toolbox — reach for them independently as each situation calls for it.