Components and Props

Chapter 6
Components and Props
Passing data into a child with the $props rune — destructuring with defaults

Chapter 1's child components were static. Props make a component reusable by letting a parent pass data in — the same concept as React props, Vue's defineProps, and Angular's @Input. Svelte 5 declares them with the $props rune, and the result reads like plain JavaScript object destructuring.

Declaring Props with $props

<!-- UserCard.svelte --> <script> let { name, age } = $props(); </script> <h3>{name}</h3> <p>Age: {age}</p>

let { name, age } = $props() declares the props this component accepts by destructuring them from the $props() rune. The destructured variables are then used directly in the markup ({name}), and — because $props is reactive — they automatically update if the parent passes new values. This is strikingly close to React's function UserCard({ name, age }) destructuring, just pulled from a rune instead of function parameters.

Passing Props from a Parent

<!-- parent --> <script> import UserCard from './UserCard.svelte'; </script> <UserCard name="Philip" age={35} /> <UserCard name="Sam" age={28} />

Import the child, then place its tag with props as attributes. The familiar string-vs-expression rule applies, exactly as in JSX: name="Philip" passes the literal string, but age={35} (with braces) passes the real number. This is identical to React's quoting rule and the same idea as Vue's :age vs age — Svelte just uses JSX-style braces rather than a binding prefix.

Default Values

let { name, age = 0, isAdmin = false } = $props();

Because props are destructured, plain JavaScript default-value syntax gives a fallback when the parent omits a prop — age = 0, isAdmin = false. No special "default" option object like Vue's or Angular's; it's just destructuring defaults, the same as React's. Clean and familiar.

Rest Props and Spreading

<!-- collect any extra props into `rest` --> let { label, ...rest } = $props();
<!-- forward them onto a real element --> <button {...rest}>{label}</button>

The rest pattern (...rest) collects any props you didn't name explicitly, and {...rest} spreads them onto an element — the standard way to build a wrapper component that forwards arbitrary attributes (e.g. a custom <Button> that still accepts disabled, type, etc.). This is exactly React's {...rest} spread, working the same way.

Adding TypeScript types is straightforward
With <script lang="ts">, props get typed by annotating the destructure: let { name, age = 0 }: { name: string; age?: number } = $props(). This gives the same compile-time prop checking that Vue's typed defineProps and Angular's typed @Input provide — and that React needs PropTypes or TS for. The course stays in plain JS, but the TS path is a one-line annotation away.

Passing Objects and Arrays

<ProductCard title="Keyboard" <!-- string --> price={49.99} <!-- number --> inStock={true} <!-- boolean --> tags={['new', 'sale']} <!-- array --> />

Anything that isn't a plain string goes in braces — numbers, booleans, arrays, objects, functions. Note Svelte uses the JS-native inStock (camelCase) attribute name directly, with no kebab-case conversion (unlike Vue's :in-stock convention) — props are passed exactly as named.

Props are read-only — don't reassign them
A child should treat props as read-only and never reassign a destructured prop — Svelte warns if you do. Props flow one way, parent to child, as in every framework. If a child needs a local editable copy, derive it (let local = $derived(name), or a separate $state initialized from the prop); to change the parent's data, use a callback prop or event — next chapter. (There's a $bindable() rune for genuine two-way prop binding, covered alongside component bind: in Chapter 7.)
SvelteReactVueAngular
let { name } = $props()params { name }defineProps(['name'])@Input() name
name="x" (string)name="x"name="x"name="x"
age={35} (real value)age={35}:age="35"[age]="35"
{ age = 0 } defaultdestructure default{ default: 0 }typed input + default
...rest + {...rest}...rest spreadv-bind="$attrs"

Coding Challenges

Challenge 1

Build a MovieCard component with title and year props (via $props), rendering "Title (Year)". Use it three times in a parent with three different movies, passing year as a real number.

📄 View solution
Challenge 2

Build a Badge component with a text prop and a color prop defaulting to "gray". Render it once with a custom color and once without, confirming the default applies.

📄 View solution
Challenge 3

Build a Button wrapper component that takes a label prop and collects all other props with ...rest, spreading them onto a real button element — then use it passing label plus extra attributes like disabled and a title.

📄 View solution

Chapter 6 Quick Reference

  • let { name, age } = $props() — declares props by destructuring the rune (= React params)
  • Used directly in markup; reactive — updates when the parent passes new values
  • name="x" passes a string; prop={expr} passes a real value (braces, like JSX)
  • Defaults are plain destructuring defaults: { age = 0 }
  • ...rest collects extra props; {...rest} forwards them onto an element
  • Props are read-only — change parent data via a callback/event (next chapter)
  • Next chapter: component communication — callback props and events