Skip to main content
Svelte & SvelteKit·Lesson 2 of 5

Stores & Component Events

When state needs to be shared across multiple components — not just between parent and child — Svelte provides stores: reactive containers that any component can subscribe to.

Writable Stores

// src/lib/stores.js
import { writable } from 'svelte/store';

export const count   = writable(0);
export const theme   = writable('light');
export const cart    = writable([]);

Subscribe in a component with the $ auto-subscription prefix:

<script>
  import { count } from '$lib/stores';
</script>

<!-- $count auto-subscribes and unsubscribes -->
<p>Count: {$count}</p>
<button on:click={()=> count.update(n=> n + 1)}>+</button>
<button on:click={()=> count.set(0)}>Reset</button>

The $store syntax is Svelte compiler sugar — it calls store.subscribe() on mount and unsubscribe() on destroy automatically.

Derived Stores

Compute values from other stores:

import { derived } from 'svelte/store';
import { cart } from './stores';

export const cartTotal = derived(
  cart,
  $cart => $cart.reduce((sum, item) => sum + item.price * item.qty, 0)
);

export const cartCount = derived(
  cart,
  $cart => $cart.reduce((sum, item) => sum + item.qty, 0)
);
<p>Total: £{$cartTotal.toFixed(2)}</p>
<p>Items: {$cartCount}</p>

derived re-runs whenever cart changes.

Readable Stores

For values that update on their own (timers, sensor data):

import { readable } from 'svelte/store';

export const time = readable(new Date(), function start(set) {
  const interval = setInterval(() => set(new Date()), 1000);
  return function stop() { clearInterval(interval); };
});

The second argument is a start function that runs when the first subscriber joins. It returns a stop function called when the last subscriber leaves.

Component Events

Child-to-parent communication uses createEventDispatcher:

<!-- TodoItem.svelte -->
<script>
  import { createEventDispatcher } from 'svelte';
  export let todo;

  const dispatch = createEventDispatcher();
</script>

<li>
  <span>{todo.text}</span>
  <button on:click={()=> dispatch('delete', todo.id)}></button>
  <button on:click={()=> dispatch('toggle', todo.id)}></button>
</li>

Parent listens with on:eventName:

<!-- TodoList.svelte -->
<script>
  import TodoItem from './TodoItem.svelte';
  let todos = [{ id: 1, text: 'Learn Svelte', done: false }];

  function deleteTodo(e)  { todos = todos.filter(t => t.id !== e.detail) }
  function toggleTodo(e)  { todos = todos.map(t => t.id === e.detail ? {...t, done: !t.done} : t) }
</script>

{#each todos as todo}
  <TodoItem {todo} on:delete={deleteTodo} on:toggle={toggleTodo} />
{/each}

Store-Driven Todo List

Ctrl+Enter
HTML
CSS
JS
Preview

Binding to Store Values

You can two-way bind directly to a store:

<script>
  import { theme } from '$lib/stores';
</script>

<select bind:value={$theme}>
  <option value="light">Light</option>
  <option value="dark">Dark</option>
</select>

<p>Current theme: {$theme}</p>

Assigning $theme = 'dark' calls theme.set('dark') under the hood.