Published 12/5/2025 · 5 min read
Tags: svelte , props , components
Lesson 4: Props and Data Flow
Components become powerful when they can receive data from their parents. In Svelte 5, props are declared using the $props rune.
Declaring Props
Use $props() to declare what data your component accepts:
<!-- Greeting.svelte -->
<script>
let { name } = $props()
</script>
<h1>Hello, {name}!</h1>
Now you can pass data to this component:
<!-- Parent.svelte -->
<script>
import Greeting from './Greeting.svelte'
</script>
<Greeting name="Gareth" />
<Greeting name="World" />
The $props() rune returns an object with all props passed to the component. You destructure the ones you need.
Default Values
Props can have default values using JavaScript destructuring defaults:
<script>
let { name = 'stranger', count = 0, active = false } = $props()
</script>
If the parent doesn’t provide a value, the default is used:
<Greeting /> <!-- Shows "Hello, stranger!" -->
<Greeting name="Jo" /> <!-- Shows "Hello, Jo!" -->
Multiple Props
Components often have several props:
<!-- Card.svelte -->
<script>
let {
title,
description = '',
image = null,
featured = false
} = $props()
</script>
<article class:featured>
{#if image}
<img src={image} alt={title} />
{/if}
<h2>{title}</h2>
{#if description}
<p>{description}</p>
{/if}
</article>
<style>
article {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
.featured {
border-color: gold;
background: #fffbeb;
}
</style>
Using it:
<Card
title="Learning Svelte"
description="A practical guide to the framework"
featured={true}
/>
<Card title="Quick Note" />
Shorthand Props
When the variable name matches the prop name, use the shorthand:
<script>
let name = 'Gareth'
let age = 35
</script>
<!-- Instead of this: -->
<Profile name={name} age={age} />
<!-- Write this: -->
<Profile {name} {age} />
This is common and makes templates cleaner.
Spread Props
If you have an object with all the props you need, spread it:
<script>
import Card from './Card.svelte'
let cardData = {
title: 'My Card',
description: 'A description',
featured: true
}
</script>
<Card {...cardData} />
This passes all properties of cardData as individual props.
Prop Types
Use TypeScript for type-safe props:
<script lang="ts">
let {
name,
count = 0,
items = []
}: {
name: string
count?: number
items?: string[]
} = $props()
</script>
Or use an interface for cleaner code:
<script lang="ts">
interface Props {
name: string
count?: number
items?: string[]
}
let { name, count = 0, items = [] }: Props = $props()
</script>
TypeScript gives you compile-time safety and editor autocompletion.
One-Way Data Flow
Data flows down from parent to child. When the parent’s data changes, the child updates. But the child shouldn’t modify props directly.
Instead, use callback props to tell the parent to update:
<!-- Counter.svelte -->
<script>
let { count, onIncrement } = $props()
</script>
<button onclick={onIncrement}>
Count: {count}
</button>
<!-- Parent.svelte -->
<script>
import Counter from './Counter.svelte'
let count = $state(0)
function handleIncrement() {
count++
}
</script>
<Counter {count} onIncrement={handleIncrement} />
The parent owns the state, the child just calls the callback. Clean and predictable.
We’ll cover events in detail in the next lesson.
Comparing to Vue
If you’re familiar with Vue, here’s how props compare:
Vue:
<script setup>
const props = defineProps({
name: String,
count: { type: Number, default: 0 },
});
</script>
Svelte 5:
<script>
let { name, count = 0 } = $props()
</script>
Both use destructuring. Vue’s defineProps and Svelte’s $props() serve the same purpose — declaring what the component accepts.
Rest Props
Use JavaScript’s rest syntax to capture additional props:
<script>
let { name, age, ...rest } = $props()
</script>
<!-- Pass all other props to a child element -->
<input {...rest} />
This is useful for wrapper components:
<!-- MyInput.svelte -->
<script>
let { label, ...rest } = $props()
</script>
<label>
{label}
<input {...rest} />
</label>
<!-- Usage -->
<MyInput
label="Email"
type="email"
placeholder="you@example.com"
required
/>
The label prop is handled by the component. Everything else (type, placeholder, required) passes through to the <input>.
Readonly Props
Props in Svelte 5 are readonly by default. If you try to reassign a prop, you’ll get an error. This enforces good data flow practices — if you need to change something, call a callback prop to tell the parent.
Practical Example
Here’s a more complete example showing props in action:
<!-- ProductCard.svelte -->
<script>
let {
product,
showDescription = true,
onSale = false,
onAddToCart
} = $props()
let displayPrice = $derived(
onSale ? product.price * 0.8 : product.price
)
</script>
<article class:on-sale={onSale}>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
{#if showDescription && product.description}
<p>{product.description}</p>
{/if}
<div class="price">
{#if onSale}
<span class="original">£{product.price.toFixed(2)}</span>
{/if}
<span class="current">£{displayPrice.toFixed(2)}</span>
</div>
<button onclick={() => onAddToCart(product)}>
Add to Cart
</button>
</article>
<style>
article {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
}
.on-sale {
border-color: #ef4444;
}
.original {
text-decoration: line-through;
color: #999;
}
.current {
font-weight: bold;
color: #16a34a;
}
</style>
<!-- Shop.svelte -->
<script>
import ProductCard from './ProductCard.svelte'
let products = $state([
{ id: 1, name: 'Widget', price: 29.99, image: '/widget.jpg' },
{ id: 2, name: 'Gadget', price: 49.99, image: '/gadget.jpg' }
])
let cart = $state([])
function addToCart(product) {
cart.push(product)
}
</script>
<div class="grid">
{#each products as product}
<ProductCard
{product}
onSale={product.id === 1}
onAddToCart={addToCart}
/>
{/each}
</div>
Key Takeaways
- Use
let { prop } = $props()to declare props - Default values use JavaScript destructuring:
let { name = 'default' } = $props() - Use shorthand
{propName}when variable matches prop name - Spread objects with
{...obj}to pass multiple props - Data flows one way: parent to child
- Use rest syntax
...restto capture additional props - Props are readonly — use callback props to communicate upward
Next: Lesson 5: Event Handling
Related Articles
- Svelte 5 Runes: A Complete Guide for Vue Developers
A comprehensive guide to Svelte 5's runes system. Learn $state, $derived, $effect, $props, and $bindable with side-by-side Vue comparisons.
- Compressed NFTs: Collections, Verification, and Building a Claim Page
Taking our cNFT minting system to production: creating verified collections, building a web-based claim flow, and preparing for mainnet deployment.
- Adding Drizzle ORM to Your SvelteKit + SQLite Setup
Level up from raw SQL to type-safe queries with Drizzle—the lightweight ORM that doesn't hide the SQL you've learned.