Data Flows Down

Components receive data from their parents through props. In Svelte 5, the $props() rune handles this. Understanding props is essential — it’s how you build configurable, reusable components.


What You’ll Learn

  • Declare props with $props()
  • Pass data to components
  • Set default values
  • Destructure multiple props

The $props Rune

Props are declared by destructuring the result of $props():

<!-- filename: src/lib/components/ServiceCard.svelte -->
<script>
  let { service } = $props();
</script>

<article class="service-card">
  <h3>{service.name}</h3>
  <p>{service.description}</p>
  <p>${service.price}</p>
</article>

The component expects a service prop. When used:

<ServiceCard service={lawnMowing} />

The lawnMowing object becomes available as service inside the component.


Passing Props

There are several ways to pass props:

Named prop:

<ServiceCard service={myService} />

Shorthand (when variable name matches prop name):

<script>
  const service = { name: 'Lawn Mowing', price: 50 };
</script>

<!-- Shorthand: {service} equals service={service} -->
<ServiceCard {service} />

Literal value:

<ServiceCard service={{ name: 'Test', price: 0 }} />

Expression:

<ServiceCard service={services.find(s => s.slug === 'lawn-mowing')} />

Multiple Props

Components can accept multiple props:

<!-- filename: src/lib/components/ServiceCard.svelte -->
<script>
  let { service, selected, showPrice } = $props();
</script>

<article class="service-card" class:selected>
  <h3>{service.name}</h3>
  <p>{service.description}</p>
  {#if showPrice}
    <p class="price">${service.price}</p>
  {/if}
</article>

Pass them when using the component:

<ServiceCard 
  service={lawnMowing} 
  selected={true} 
  showPrice={true} 
/>

<!-- Or with shorthand where applicable -->
<ServiceCard {service} {selected} showPrice />

Boolean props can be passed without a value — showPrice equals showPrice={true}.


Default Values

Set defaults for optional props:

<script>
  let { 
    service,
    selected = false,
    showPrice = true,
    showDuration = true
  } = $props();
</script>

Now these work:

<!-- Uses defaults: selected=false, showPrice=true, showDuration=true -->
<ServiceCard {service} />

<!-- Override specific defaults -->
<ServiceCard {service} selected={true} showDuration={false} />

Required props (no default) will cause a warning if not provided.


Props Are Read-Only

Props flow down from parent to child. You cannot reassign them:

<script>
  let { service } = $props();
  
  // ❌ This will cause an error
  service = { name: 'New Service' };
</script>

If you need to modify prop data, create local state:

<script>
  let { service } = $props();
  
  // ✅ Create local state from prop
  let localService = $state({ ...service });
  
  function updateName(newName) {
    localService.name = newName;
  }
</script>

Reactive Props

Props are automatically reactive. When the parent updates the data, the child re-renders:

<!-- Parent -->
<script>
  let currentService = $state(services[0]);
  
  function selectNext() {
    const index = services.indexOf(currentService);
    currentService = services[(index + 1) % services.length];
  }
</script>

<ServiceCard service={currentService} />
<button onclick={selectNext}>Next Service</button>

When currentService changes, ServiceCard automatically updates.


Accessing All Props

Sometimes you need all props as an object:

<script>
  let props = $props();
  
  // Access individual props
  console.log(props.service);
  console.log(props.selected);
</script>

Or combine destructuring with rest:

<script>
  let { service, ...rest } = $props();
  
  // service is extracted
  // rest contains all other props
</script>

This is useful for forwarding props to other elements.


Update ServiceCard with Props

Let’s enhance the ServiceCard with proper props:

<!-- filename: src/lib/components/ServiceCard.svelte -->
<script>
  let { 
    service,
    selected = false,
    showDescription = true,
    showPrice = true,
    showDuration = true
  } = $props();
</script>

<article class="service-card" class:selected>
  <h3>
    <a href="/services/{service.slug}">{service.name}</a>
  </h3>
  
  {#if showDescription && service.description}
    <p class="description">{service.description}</p>
  {/if}
  
  <div class="meta">
    {#if showPrice}
      <span class="price">${service.price}</span>
    {/if}
    {#if showDuration}
      <span class="duration">{service.duration} min</span>
    {/if}
  </div>
</article>

<style>
  .service-card {
    padding: 1rem;
    border: 2px solid #ddd;
    border-radius: 8px;
    background: white;
    transition: all 0.2s;
  }
  
  .service-card:hover {
    border-color: #007bff;
  }
  
  .service-card.selected {
    border-color: #007bff;
    background: #f0f7ff;
  }
  
  .service-card h3 {
    margin: 0 0 0.5rem 0;
  }
  
  .service-card h3 a {
    color: inherit;
    text-decoration: none;
  }
  
  .description {
    color: #666;
    margin: 0 0 0.75rem 0;
  }
  
  .meta {
    display: flex;
    gap: 1rem;
  }
  
  .price {
    font-weight: bold;
    color: #007bff;
  }
  
  .duration {
    color: #666;
    font-size: 0.875rem;
  }
</style>

Now use it with various configurations:

<!-- Full display -->
<ServiceCard {service} />

<!-- Selected state -->
<ServiceCard {service} selected={true} />

<!-- Compact (no description) -->
<ServiceCard {service} showDescription={false} />

<!-- Minimal -->
<ServiceCard {service} showDescription={false} showDuration={false} />

Common Mistakes

Forgetting to Destructure

<script>
  // ❌ Props aren't accessible
  $props();
  
  // ✅ Destructure to access
  let { service } = $props();
</script>

Trying to Mutate Props

<script>
  let { items } = $props();
  
  // ❌ Can't push to a prop
  items.push(newItem);
  
  // ✅ Tell parent to update, or use local state
  let localItems = $state([...items]);
  localItems.push(newItem);
</script>

Missing Required Props

<!-- ❌ service is required but not provided -->
<ServiceCard />

<!-- ✅ Provide required props -->
<ServiceCard {service} />

Summary

Props are how data flows into components. Use $props() to declare them, destructure to access individual props, and set defaults for optional ones. Props are reactive and read-only.

Key takeaways:

  • Declare with let { prop } = $props()
  • Set defaults: let { prop = defaultValue } = $props()
  • Props are read-only — don’t reassign them
  • Props automatically trigger re-renders when changed

Next Steps

ServiceCard is looking good. Continue with Create BookingForm Component to extract the form logic into its own component.