Svelte 5 offers multiple ways for components to communicate. Let's understand when to use each.
1. Props (Parent → Child)
The simplest pattern — pass data down:
svelte<!-- Parent --> <UserCard name={user.name} avatar={user.avatar} /> <!-- Child --> <script> let { name, avatar } = $props(); </script>
2. Callback Props (Child → Parent)
Pass functions down, child calls them to communicate up:
svelte<!-- Parent --> <script> function handleDelete(id) { items = items.filter(i => i.id !== id); } </script> <ItemList items={items} ondelete={handleDelete} /> <!-- Child --> <script> let { items, ondelete } = $props(); </script> {#each items as item} <li> {item.name} <button onclick={() => ondelete(item.id)}>Delete</button> </li> {/each}
3. Bindable Props (Two-Way)
When parent and child both need to read/write:
svelte<!-- Parent --> <Modal bind:open={showModal}> <p>Modal content</p> </Modal> <!-- Child --> <script> let { open = $bindable(false), children } = $props(); </script> {#if open} <div class="modal"> {@render children()} <button onclick={() => open = false}>Close</button> </div> {/if}
4. Context (Deep Passing)
When data needs to skip intermediate components:
svelte<!-- App.svelte --> <script> import { setContext } from 'svelte'; setContext('theme', { mode: 'dark' }); </script> <!-- DeepChild.svelte (many levels down) --> <script> import { getContext } from 'svelte'; const theme = getContext('theme'); </script>
5. Shared State (.svelte.js)
For truly global state:
js// store.svelte.js export const notifications = $state([]); export function addNotification(message) { notifications.push({ id: Date.now(), message }); }
Decision Tree
Does data flow DOWN only?
→ Props
Does child need to NOTIFY parent?
→ Callback props
Do both need to READ and WRITE?
→ $bindable
Is there DEEP nesting?
→ Context
Is state truly GLOBAL?
→ .svelte.js module