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, this happens through props — and the syntax is delightfully simple.
Declaring Props
To make a variable a prop, export it:
<!-- Greeting.svelte -->
<script>
export let name
</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" />
That’s it. export let declares a prop.
Default Values
Props can have default values:
<script>
export let name = 'stranger'
export let count = 0
export let active = false
</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>
export let title
export let description = ''
export let image = null
export let featured = false
</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
Svelte doesn’t have built-in prop validation like Vue or React PropTypes. But you can use TypeScript:
<script lang="ts">
export let name: string
export let count: number = 0
export let items: string[] = []
</script>
Or add runtime checks if needed:
<script>
export let count
$: if (typeof count !== 'number') {
console.warn('count should be a number')
}
</script>
For most projects, TypeScript is the right answer.
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:
<!-- Don't do this -->
<script>
export let count
function increment() {
count++ // Modifying a prop directly
}
</script>
This technically works, but it creates confusing data flow. The parent thinks it owns count, but the child is changing it.
Instead, emit an event to tell the parent to update:
<!-- Counter.svelte -->
<script>
import { createEventDispatcher } from 'svelte'
export let count
const dispatch = createEventDispatcher()
function increment() {
dispatch('increment')
}
</script>
<button onclick={increment}>
Count: {count}
</button>
<!-- Parent.svelte -->
<script>
import Counter from './Counter.svelte'
let count = 0
function handleIncrement() {
count++
}
</script>
<Counter {count} on:increment={handleIncrement} />
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:
<script>
export let name
export let count = 0
</script>
Vue is more explicit about types (without TypeScript). Svelte is more concise.
The $$props and $$restProps
Sometimes you need access to all props or props you haven’t explicitly declared:
<script>
export let name
export let age
// $$props contains all props passed to the component
// $$restProps contains props NOT declared with export
</script>
<!-- Pass all undeclared props to a child element -->
<input {...$$restProps} />
This is useful for wrapper components:
<!-- MyInput.svelte -->
<script>
export let label
</script>
<label>
{label}
<input {...$$restProps} />
</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
If you want to make it clear a prop shouldn’t be modified, you can use const:
<script>
export const version = '1.0.0'
</script>
But this is rare. Usually you just don’t modify props.
Practical Example
Here’s a more complete example showing props in action:
<!-- ProductCard.svelte -->
<script>
import { createEventDispatcher } from 'svelte'
export let product
export let showDescription = true
export let onSale = false
const dispatch = createEventDispatcher()
$: displayPrice = 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={() => dispatch('addToCart', 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 = [
{ id: 1, name: 'Widget', price: 29.99, image: '/widget.jpg' },
{ id: 2, name: 'Gadget', price: 49.99, image: '/gadget.jpg' }
]
let cart = []
function addToCart(event) {
cart = [...cart, event.detail]
}
</script>
<div class="grid">
{#each products as product}
<ProductCard
{product}
onSale={product.id === 1}
on:addToCart={addToCart}
/>
{/each}
</div>
Key Takeaways
- Use
export letto declare a prop - Props can have default values:
export let name = 'default' - Use shorthand
{propName}when variable matches prop name - Spread objects with
{...obj}to pass multiple props - Data flows one way: parent to child
- Use
$$restPropsto pass through undeclared props - Don’t mutate props directly — use events instead
Next: Lesson 5: Event Handling
Related Articles
- x402 with SvelteKit: Full-Stack Example
Build a complete SvelteKit application with x402 payments - wallet connection, protected routes, and automatic payment handling.
- Interacting with Programs from Svelte
Build Svelte components that interact with Solana programs - token balances, transfers, and real-time updates.
- Signing Messages and Transactions in the Browser
Learn to sign messages for authentication and build transactions that users approve through their wallet.