DEV Community

Cover image for Svelte Events & Bindings Tutorial: Master Parent-Child Communication
Ali Aslam
Ali Aslam

Posted on

Svelte Events & Bindings Tutorial: Master Parent-Child Communication

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>
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

Breaking It Down

  1. In the child (ChildButton.svelte):
  • let { onHello } = $props(); declares a prop named onHello. The parent is expected to pass in a function.
  • Inside handleClick, we call onHello?.(). The ?. means “only call it if it exists.”
  1. In the parent (+page.svelte):
  • <ChildButton onHello={handleHello} /> passes the parent’s handleHello function as the child’s onHello 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>
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

Breaking It Down

  1. Child (NameForm.svelte)
  • let { onSubmit } = $props(); declares a callback prop.
  • Inside submitForm(), we call onSubmit?.({ name }). That’s the child sending a backpack with { name: "something" }.
  1. Parent (+page.svelte)
  • <NameForm onSubmit={handleSubmit} /> gives the child a function to call.
  • The parent’s handler receives { name } directly.
  1. |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:

  1. $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.”

  1. 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} />
Enter fullscreen mode Exit fullscreen mode

src/routes/+page.svelte

<script>
  import TextInput from '$lib/TextInput.svelte';
  let name = $state('');
</script>

<h1>Hello, {name || "stranger"}!</h1>
<TextInput bind:text={name} />
Enter fullscreen mode Exit fullscreen mode

🔎 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

The parent must pass:

<Child onHello={...} />
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode

❌ 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)