Imagine you’re building an app with buttons, forms, checkboxes — the usual gang. One of the first questions you’ll run into is:
👉 “How do my components actually talk to each other?”
This is the heart of parent–child communication in Svelte. And just like in real life, communication goes both ways:
- A parent can hand data down to a child. (Props 📦)
- A child can send a message back up to the parent. (Events 📢)
- And sometimes they both scribble on the same shared notepad. (Bindings 📝)
In this article, we’re going to zoom in on the child → parent and shared state parts: events and bindings. By the end, you’ll know exactly how to make data flow in all directions.
Setup Reminder 🛠️
Here’s our project skeleton:
-
src/routes
→ holds your pages. -
src/routes/+page.svelte
→ the main entry page. -
src/lib
→ where reusable components (buttons, inputs, forms, etc.) live.
For everything we build today:
- Components go into
src/lib
. - We’ll test them inside
src/routes/+page.svelte
.
Section 1: Events — Children Talking to Parents 🗣️
Props are great for downward communication: the parent says, “Here’s your lunchbox 🍱.”
But what if the child needs to say:
- “I finished my sandwich!”
- “I spilled juice everywhere!” 🧃💥
- “Please give me cookies instead.” 🍪
That’s where events come in. Events let a child raise its hand and shout upward:
“Hey parent, something happened!”
Mental Model of Events
- Props = parent tells child what to do.
- Events = child notifies parent about what it just did.
Think of props as instructions, and events as a megaphone back up to the parent.
Example 1: A Simple “Hello” Event
Let’s build a button that, when clicked, yells “hello!” at its parent.
src/lib/ChildButton.svelte
<script>
// Parent will pass down a callback function named onHello
let { onHello } = $props();
function handleClick() {
// Call the parent’s callback when the button is clicked
onHello?.();
}
</script>
<button on:click={handleClick}>Say Hello</button>
src/routes/+page.svelte
<script>
import ChildButton from '$lib/ChildButton.svelte';
function handleHello() {
alert("Child says hello!");
}
</script>
<h1>Events Example</h1>
<ChildButton onHello={handleHello} />
Breaking It Down
- In the child (
ChildButton.svelte
):
-
let { onHello } = $props();
declares a prop namedonHello
. The parent is expected to pass in a function. - Inside
handleClick
, we callonHello?.()
. The?.
means “only call it if it exists.”
- In the parent (
+page.svelte
):
-
<ChildButton onHello={handleHello} />
passes the parent’shandleHello
function as the child’sonHello
prop. - When the child calls
onHello()
, the parent’s function runs.
💡 This replaces the old createEventDispatcher
from Svelte 3/4. No special dispatcher is needed — just plain callback props.
Example 2: Sending Data with Events
Events aren’t just about saying “hey, something happened.”
They can also carry data backpacks 🎒 — letting children pass information upward.
Let’s build a form where the child tells the parent what name was entered.
src/lib/NameForm.svelte
<script>
let { onSubmit } = $props();
let name = $state('');
function submitForm() {
// Call the parent’s callback with data
onSubmit?.({ name });
// Reset the input
name = '';
}
</script>
<form on:submit|preventDefault={submitForm}>
<input bind:value={name} placeholder="Enter your name" />
<button type="submit">Submit</button>
</form>
src/routes/+page.svelte
<script>
import NameForm from '$lib/NameForm.svelte';
function handleSubmit({ name }) {
alert(`Form submitted with name: ${name}`);
}
</script>
<h1>Form Event Example</h1>
<NameForm onSubmit={handleSubmit} />
Breaking It Down
- Child (
NameForm.svelte
)
-
let { onSubmit } = $props();
declares a callback prop. - Inside
submitForm()
, we callonSubmit?.({ name })
. That’s the child sending a backpack with{ name: "something" }
.
- Parent (
+page.svelte
)
-
<NameForm onSubmit={handleSubmit} />
gives the child a function to call. - The parent’s handler receives
{ name }
directly.
|preventDefault
modifier
- Prevents the form from reloading the page.
- Keeps the interaction in SPA-land.
Quick Recap
At this point you’ve seen two flavors of events:
- Simple ping: child says “Hello!” (no data).
- Data carrier: child says “Here’s the form data!” (with a backpack).
💡 Mental model:
- Callback prop = the megaphone the parent hands down.
- Child calling it = shouting into that megaphone.
- Parent function = the ear that hears and reacts.
✅ With this, you’ve mastered child → parent communication in Svelte 5.
Up next: we’ll look at bindings — the magical way for parent and child to share the same piece of data.
Section 2: Bindings — Sharing State 🔗
Okay, so props let parents talk down to kids.
Events let kids shout back up to parents.
But sometimes, honestly… that’s too much chatter.
Imagine a parent yelling, “Write down your chores!” and the kid yelling back, “Okay I did it!” for every single scribble. 😵💫
Wouldn’t it be easier if they just shared the same notepad?
That’s what bindings do. Both parent and child keep their pens on the same page — when one writes, the other instantly sees it.
Two Ingredients of Binding
Bindings in Svelte 5 rely on two key ideas:
$bindable()
- Normally, props are one-way: the parent sets them, the child can read them, but that’s it.
- If you want a prop to sync in both directions, mark it with
$bindable()
. -
Example:
let { text = $bindable() } = $props();
Translation: “This child accepts a prop called
text
, and it can be updated both by the parent and the child.”
bind:<property>
- Special Svelte syntax that links a variable to an element’s property.
-
Examples:
-
bind:value
for text inputs. -
bind:checked
for checkboxes/radios. -
bind:open
for<details>
.
-
Works on plain HTML elements and on
$bindable
props of child components.
Together, $bindable()
and bind:
are the glue that makes parent ↔ child sharing possible.
Example 1: Binding Text Input
Let’s make a reusable text input that syncs directly with parent state.
src/lib/TextInput.svelte
<script>
let { text = $bindable() } = $props();
</script>
<input bind:value={text} />
src/routes/+page.svelte
<script>
import TextInput from '$lib/TextInput.svelte';
let name = $state('');
</script>
<h1>Hello, {name || "stranger"}!</h1>
<TextInput bind:text={name} />
🔎 What’s happening?
- The child’s
text
prop is marked$bindable()
. - The parent uses
bind:text={name}
to connect its state. - Typing updates
name
in the parent instantly. - If the parent changes
name
, the input updates too.
That’s the magic of shared state.
Example 2: Binding a Checkbox
Bindings aren’t just for text inputs — checkboxes, sliders, selects, even media elements all play nice.
src/lib/CheckBox.svelte
<script>
let { checked = $bindable() } = $props();
</script>
<label>
<input type="checkbox" bind:checked={checked} />
Accept Terms
</label>
src/routes/+page.svelte
<script>
import CheckBox from '$lib/CheckBox.svelte';
let accepted = $state(false);
</script>
<CheckBox bind:checked={accepted} />
<p>{accepted ? "✅ Accepted" : "❌ Not accepted"}</p>
Now flipping the checkbox updates accepted
in the parent — no event shouting required.
🔑 Mental model recap:
- Props = parent writes in the child’s notebook.
- Events = child shouts back to the parent.
- Bindings = they both write in the same notebook.
Bindings = instant synchronization, without extra ceremony.
Section 3: Events vs Bindings — Which One to Use? 🤔
So now you know two tools:
- Events = notifications.
- Bindings = shared state.
But when should you use which?
Here’s a cheat sheet:
Use Events When…
- A button click matters → notify the parent.
- A form submission happens → parent needs the data once.
- Any one-off action → “Hey, something just happened.”
Events are like raising your hand in class. ✋
Use Bindings When…
- A text field should always match parent state.
- A toggle or checkbox needs to stay in sync.
- Sliders, selects, range inputs → anything “live” both sides care about.
Bindings are like a shared Google Doc 📝 — both can edit, both always see the latest.
👉 Analogy time:
- Events = kid says “I finished my homework!”
- Bindings = parent and kid share the family calendar. Whoever writes “Dentist 3pm,” both instantly see it.
Neither tool is better — you just pick the one that matches the situation.
Section 4: Gotchas with Events & Bindings ⚠️
Before you run wild, let’s cover common pitfalls.
1. Callback Names Are Case-Sensitive
If the child expects:
let { onHello } = $props();
The parent must pass:
<Child onHello={...} />
If you write onhello
or on:hello
(the old syntax), it won’t work. Stick to exact names.
2. Forgetting to Call the Callback
Declaring let { onSave } = $props();
doesn’t fire anything by itself.
The child must actually call onSave?.()
when something happens.
3. Bindings Only Work on Supported Properties
Bindings aren’t “bind anything” magic. They only work on specific properties that Svelte supports, such as:
-
bind:value
→<input>
,<textarea>
,<select>
-
bind:checked
→ checkboxes, radios -
bind:open
→<details>
-
bind:currentTime
,bind:paused
→<video>
/<audio>
Try bind:whatever
and nothing happens. 👉 When in doubt, check the Svelte bind docs.
4. Don’t Bind Everything
Bindings are powerful, but too many can tangle your state.
Rule of thumb:
- Use bindings for form fields, toggles, sliders.
- Use events for bigger actions like “save” or “submit.”
Analogy: bindings are like letting everyone edit the same grocery list 🛒. Great for shopping, but chaos if you’re planning a wedding 💍.
5. Binding to Derived Values Doesn’t Work
You can only bind to stateful variables ($state
or $bindable
props).
For example:
let price = $state(100);
let discounted = $derived(price * 0.9);
<input bind:value={discounted} />
❌ This fails — discounted
is read-only.
✅ Instead, bind to the source (price
) and compute discounted
separately.
Section 5: Wrap-Up 🎯
Let’s recap the toolkit with our trusty analogies:
- Props = 🥪 parent packs a lunchbox → data flows down.
- Events = 🙋 child raises a hand → notifications flow up.
- Bindings = 📝 shared notebook → both stay in sync.
Together, they form the family communication system of Svelte:
- Parents guide kids with props.
- Kids notify parents with events (via callback props).
- Parents + kids share data with bindings.
That’s full-circle communication. 🎉
Where We Are
You’ve now seen the three pillars of Svelte component communication:
✅ Props → push data down.
✅ Events → send messages up.
✅ Bindings → keep parent and child in sync.
Master these, and you’ve already solved 90% of real-world communication needs.
But what if you want components that don’t just talk to their direct parent, but can be reused in any context — no matter who’s listening?
👉 Up next: [event forwarding and advanced patterns to make components more flexible.](https://dev.to/a1guy/svelte-events-bindings-tutorial-master-parent-child-communication-4o5o)
Top comments (0)