Props with $props

Svelte · Essentials · 10 min

Props are how components are configured and composed; every reusable component has them.

Components receive inputs as props. Inside the child, read them with the `$props` rune and destructure:

let { title, count = 0, ...rest } = $props();
  • Default values use normal destructuring defaults (count = 0), applied when the parent passes undefined.
  • Rename with { class: klass }, useful for reserved words.
  • ...rest collects everything else, handy for forwarding attributes to an element: <button {...rest}>.
  • With TypeScript, annotate: let { title, count = 0 }: { title: string; count?: number } = $props();.

The parent passes props as attributes: <Counter title="Clicks" count={5} />, {title} shorthand when the variable has the same name, and {...obj} to spread an object.

Props are reactive: when the parent changes the value, destructured variables update too (the compiler makes this work). They are not meant to be reassigned by the child. You can temporarily reassign a prop locally (it will be overwritten when the parent passes a new value), but mutating an object prop the child doesn't own triggers a dev warning (ownership_invalid_mutation). To let a child change parent state, pass a callback prop or use a bindable prop (later lessons).

Every prop is optional unless typed otherwise; missing ones are undefined. $props.id() (5.20+) generates an id unique to the component instance, consistent between server and client, for for/id pairs.

Example

<!-- Badge.svelte -->
<script>
  let { label, tone = 'info', count = 0, ...rest } = $props();
</script>

<span class="badge {tone}" {...rest}>
  {label}{#if count > 0}<b> {count}</b>{/if}
</span>

<!-- App.svelte -->
<script>
  import Badge from './Badge.svelte';
  let unread = $state(3);
  let label = 'Inbox';
</script>

<Badge {label} count={unread} title="Unread messages" />  <!-- title goes via rest -->
<Badge label="Spam" tone="warn" />                         <!-- count defaults to 0 -->
<button onclick={() => unread++}>new mail</button>

Remember

  • let { a, b = default, ...rest } = $props() reads props
  • Destructured props stay reactive to parent changes
  • Don't mutate props you don't own; use callbacks or bindable props

Watch out for

  • Mutating an object prop in the child to communicate with the parent; it's a dev warning and makes data flow hard to follow.

Check yourself

  1. A parent renders `<Badge count={undefined} />` and Badge declares `let { count = 5 } = $props()`. What is count?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: svelte.dev/docs/svelte/$props