iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Components

Components are Svelte SFCs: script, markup, style. Props in, events out, slots for projection — and a compiler that does the diff work for you.

Svelte — components essentials

EXAMPLE
<!-- ===== Counter.svelte ===== -->
<script>
  // Props (Svelte 4 style; runes-based Svelte 5 differs)
  export let label = 'count';
  export let start = 0;

  let count = start;
  $: doubled = count * 2;       // reactive declaration

  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();

  function inc() {
    count += 1;
    dispatch('change', count);
  }
</script>

<button on:click={inc}>
  {label}: {count} (x2 = {doubled})
</button>

<style>
  button { padding: 0.5rem 1rem; border-radius: 8px; }
  button:hover { background: #eef; }
  /* styles are scoped to this component automatically */
</style>

<!-- ===== App.svelte (using Counter + slots) ===== -->
<script>
  import Counter from './Counter.svelte';
  let total = 0;
  function onChange(e) { total += e.detail; }
</script>

<Counter label="clicks" start={0} on:change={onChange} />
<Counter label="votes"  start={5} on:change={onChange} />

<p>Total: {total}</p>

<!-- ===== Slots ===== -->
<!-- Card.svelte -->
<div class="card">
  <header><slot name="title">Default title</slot></header>
  <div><slot /></div>
  <footer><slot name="actions" /></footer>
</div>

<!-- Use Card -->
<Card>
  <h2 slot="title">Order #42</h2>
  <p>Two items, AUD 49.95</p>
  <button slot="actions">Pay</button>
</Card>

<!-- ===== Bind: two-way binding helpers ===== -->
<!-- Parent owns the value; child mutates via bind: -->
<input bind:value={name} />
<Counter bind:start={initial} />     <!-- child prop becomes two-way -->

<!-- ===== Lifecycle ===== -->
<script>
  import { onMount, onDestroy, tick } from 'svelte';
  onMount(() => {
    const t = setInterval(() => count++, 1000);
    return () => clearInterval(t);   // cleanup
  });
  onDestroy(() => { /* fallback for non-mount path */ });
  async function focusNext() { await tick(); inputEl.focus(); }
</script>

<!-- ===== Stores (cross-component state) ===== -->
<!-- stores.js -->
import { writable, derived } from 'svelte/store';
export const cart = writable([]);
export const total = derived(cart, ($cart) => $cart.reduce((s, l) => s + l.price, 0));

<!-- in any .svelte file -->
<script>
  import { cart, total } from './stores.js';
</script>
<p>Items: {$cart.length}, total: {$total}</p>
<button on:click={() => cart.update((c) => [...c, { price: 10 }])}>Add</button>

<!-- ===== Patterns to internalise =====
- export let for props; defaults double as the documentation
- $: for derived state; the compiler tracks deps automatically
- createEventDispatcher + on:event for explicit child-to-parent comms
- Slots for content projection; named slots for layouts
- writable/derived stores for cross-component state; no context API ceremony

===== Pitfalls =====
- Mutating arrays/objects in place doesn't trigger updates -> reassign (arr = [...arr])
- bind: makes prop two-way; remember the parent now owns the rebind
- $: reactive blocks run in declaration order; avoid forward references
- Forgetting cleanup in onMount returns -> intervals/listeners leak
- Mixing stores and prop drilling -> pick a direction per concern
-->

Why it matters

Components are the unit Svelte is built around: small, single-file, scoped styles, compile-time reactivity. Props in, events out, slots for layout — and stores when state crosses trees. The mental model that gets you furthest is "describe the shape; the compiler does the diff".

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<!-- Counter.svelte -->
<script>
    let count = $state(0);
</script>
<button onclick={() => count++}>Clicked {count}</button>
Try it Yourself »

Discussion

Loading…