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

Props

Component props in Svelte 5 are declared via the \$props rune. Destructure with defaults; mark bindable for two-way binding; use rest for spread.

Props, defaults, bindable, spread

EXAMPLE
<!-- 1) Declare props with the $props rune -->
<script>
    let { title, count = 0, items = [] } = $props();
</script>

<h2>{title}</h2>
<p>Count: {count}</p>
<ul>
    {#each items as item}
        <li>{item.name}</li>
    {/each}
</ul>

<!-- Parent -->
<UserCard title="Hello" count={3} items={users} />

<!-- 2) TypeScript prop types -->
<script lang="ts">
    interface Props {
        title:    string;
        count?:   number;
        items:    Item[];
        onSelect?: (i: Item) => void;
    }
    let { title, count = 0, items, onSelect }: Props = $props();
</script>

<!-- 3) Rest props — capture extra attributes -->
<script>
    let { class: className = '', ...rest } = $props();
</script>

<button class="btn ${className}" {...rest}>
    <slot />
</button>

<!-- Pass id, aria-*, data-* etc through to the underlying element -->
<MyButton id="save" aria-label="Save" data-testid="btn-save">Save</MyButton>

<!-- 4) Bindable props — two-way binding -->
<!-- TextInput.svelte -->
<script>
    let { value = $bindable('') } = $props();
</script>

<input bind:value />

<!-- Parent -->
<script>
    let name = $state('');
</script>
<TextInput bind:value={name} />
<p>{name}</p>

<!-- 5) Children — the children of a component -->
<!-- Layout.svelte -->
<script>
    let { children, footer } = $props();
</script>

<header>Site header</header>
<main>
    {@render children?.()}
</main>
<footer>
    {#if footer}{@render footer()}{/if}
</footer>

<!-- Parent -->
<Layout>
    {#snippet footer()}
        <p>© 2026</p>
    {/snippet}

    <article>Body content</article>
</Layout>

<!-- 6) Default values for falsy props -->
<script>
    let { user = { name: 'Anonymous', avatar: '/default.png' } } = $props();
</script>

<!-- 7) Reactive props — they auto-update when the parent changes them -->
<script>
    let { count } = $props();
    let double = $derived(count * 2);    // updates when parent changes count
    $effect(() => console.log('count changed to', count));
</script>

<!-- 8) Validate props at runtime (no formal API; use a custom check) -->
<script>
    let { rating } = $props();
    $effect(() => {
        if (typeof rating !== 'number' || rating < 0 || rating > 5) {
            console.warn('Invalid rating:', rating);
        }
    });
</script>

<!-- 9) Slots — children + named slots (Svelte 4 compat) -->
<!-- Card.svelte (Svelte 4 syntax — still works in 5) -->
<div class="card">
    <header>
        <slot name="title">Default title</slot>
    </header>
    <div class="body">
        <slot>Default body</slot>
    </div>
    <footer>
        <slot name="actions" />
    </footer>
</div>

<!-- Parent -->
<Card>
    <h2 slot="title">My title</h2>
    <p>Body content</p>
    <button slot="actions">OK</button>
</Card>

<!-- Svelte 5 prefers snippets over slots for new code (more powerful + typed). -->

<!-- 10) Forwarding events -->
<!-- Svelte 5: event handlers are just props named onclick / onsubmit / etc. -->
<!-- Parent passes a handler; child invokes it -->
<!-- Child -->
<script>
    let { onclick } = $props();
</script>
<button {onclick}>Click</button>

<!-- Parent -->
<MyButton onclick={() => count++} />

<!-- 11) Common patterns -->

<!-- Polymorphic component — render as a different tag -->
<script>
    let { as = 'div', children, ...rest } = $props();
    let Tag = $derived(as);
</script>
<svelte:element this={Tag} {...rest}>
    {@render children()}
</svelte:element>

<!-- Conditional props -->
<MyInput type={isPassword ? 'password' : 'text'} />

<!-- 12) Common bugs -->
<!--
  • Forgetting $bindable — bind:value on a non-bindable prop is an error
  • Destructuring with defaults at the top of script BUT the rest object loses reactivity
     → always use { ...rest } after defaults
  • Modifying props directly — they're reactive READ-ONLY; emit events / use $bindable
  • Missing keys in {#each} → wrong child instances reused on reorder
-->

Why it matters

Svelte 5 props are runes-driven: \$props() for inputs, \$bindable for two-way, {@render children()} for slot equivalents. Cleaner and more typed than slots, with full Svelte reactivity.

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

Example

Example
<!-- Child.svelte -->
<script>
    let { name, age = 0 } = $props();
</script>
<h2>{name} ({age})</h2>
Try it Yourself »

Discussion

Loading…