Extract the Service Card

The services page and booking form both display service information. Instead of duplicating that markup, let’s create a reusable ServiceCard component.


What You’ll Learn

  • Create a new component file
  • Move markup into a component
  • Import and use components
  • Organize component files

The Repeated Pattern

Both places show services similarly:

<!-- In the services listing -->
{#each services as service}
  <article class="service-card">
    <h3>{service.name}</h3>
    <p>{service.description}</p>
    <p class="price">${service.price}</p>
    <p class="duration">{service.duration} minutes</p>
  </article>
{/each}

<!-- In the booking form -->
{#each services as service}
  <label class="service-card">
    <input type="radio" ... />
    <h3>{service.name}</h3>
    <p>{service.description}</p>
    <span class="price">${service.price}</span>
  </label>
{/each}

Same structure, slightly different context. Perfect for a component.


Create the Component File

Create a new file in the components folder:

<!-- filename: src/lib/components/ServiceCard.svelte -->
<script>
  // We'll add props here shortly
</script>

<article class="service-card">
  <h3>Service Name</h3>
  <p>Service description goes here.</p>
  <p class="price">$50</p>
  <p class="duration">60 minutes</p>
</article>

<style>
  .service-card {
    padding: 1rem;
    border: 1px solid #ddd;
    border-radius: 8px;
    background: white;
  }
  
  .service-card h3 {
    margin: 0 0 0.5rem 0;
  }
  
  .service-card p {
    margin: 0.25rem 0;
    color: #666;
  }
  
  .price {
    font-weight: bold;
    color: #007bff;
  }
  
  .duration {
    font-size: 0.875rem;
  }
</style>

This is a static component — it doesn’t accept any data yet. That comes next.


Import and Use the Component

Use the component in the services page:

<!-- filename: src/routes/services/+page.svelte -->
<script>
  import ServiceCard from '$lib/components/ServiceCard.svelte';
</script>

<h1>Our Services</h1>

<div class="services-grid">
  <ServiceCard />
  <ServiceCard />
  <ServiceCard />
</div>

<style>
  .services-grid {
    display: grid;
    gap: 1rem;
  }
</style>

Three identical cards appear. They all show “Service Name” because the component is still static.


The $lib Alias

Notice the import path: $lib/components/ServiceCard.svelte.

$lib is a SvelteKit alias pointing to src/lib/. It provides:

  • Consistent imports from anywhere
  • No relative path confusion (../../..)
  • Works in both components and pages

The src/lib folder is for shared code: components, utilities, stores.


File Organization

A typical component structure:

src/lib/
├── components/
│   ├── ServiceCard.svelte
│   ├── BookingForm.svelte
│   ├── PriceBadge.svelte
│   └── layout/
│       ├── Header.svelte
│       └── Footer.svelte
├── stores/
│   └── cart.js
└── utils/
    └── formatting.js

Group related components in subfolders as the project grows.


Make It Dynamic

The component needs to display different services. We’ll pass data as props:

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

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

<style>
  /* Same styles as before */
</style>

Now pass the data when using it:

<!-- filename: src/routes/services/+page.svelte -->
<script>
  import ServiceCard from '$lib/components/ServiceCard.svelte';
  
  const services = [
    {
      id: '1',
      slug: 'lawn-mowing',
      name: 'Lawn Mowing',
      description: 'Professional lawn mowing service.',
      price: 50,
      duration: 60
    },
    {
      id: '2',
      slug: 'hedge-trimming',
      name: 'Hedge Trimming',
      description: 'Expert hedge and shrub trimming.',
      price: 75,
      duration: 90
    }
  ];
</script>

<h1>Our Services</h1>

<div class="services-grid">
  {#each services as service (service.id)}
    <ServiceCard {service} />
  {/each}
</div>

The {service} shorthand is equivalent to service={service}.


Make the card clickable:

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

<article class="service-card">
  <h3>
    <a href="/services/{service.slug}">{service.name}</a>
  </h3>
  <p>{service.description}</p>
  <p class="price">${service.price}</p>
  <p class="duration">{service.duration} minutes</p>
</article>

<style>
  .service-card {
    padding: 1rem;
    border: 1px solid #ddd;
    border-radius: 8px;
    background: white;
    transition: border-color 0.2s;
  }
  
  .service-card:hover {
    border-color: #007bff;
  }
  
  .service-card h3 {
    margin: 0 0 0.5rem 0;
  }
  
  .service-card h3 a {
    color: inherit;
    text-decoration: none;
  }
  
  .service-card h3 a:hover {
    color: #007bff;
  }
  
  .service-card p {
    margin: 0.25rem 0;
    color: #666;
  }
  
  .price {
    font-weight: bold;
    color: #007bff !important;
  }
  
  .duration {
    font-size: 0.875rem;
  }
</style>

Verify It Works

The services page should now show:

  1. Multiple service cards
  2. Each with its own data
  3. Links to detail pages
  4. Hover effects

If you edit ServiceCard.svelte, changes appear everywhere it’s used. That’s the power of components.


Common Mistakes

Forgetting the Import

<!-- ❌ Component not imported -->
<ServiceCard {service} />

<!-- Error: ServiceCard is not defined -->

Always import components before using them.

Wrong Import Path

<!-- ❌ Relative path (fragile) -->
import ServiceCard from '../../../lib/components/ServiceCard.svelte';

<!-- ✅ Use $lib alias -->
import ServiceCard from '$lib/components/ServiceCard.svelte';

Missing .svelte Extension

<!-- ❌ Missing extension -->
import ServiceCard from '$lib/components/ServiceCard';

<!-- ✅ Include .svelte -->
import ServiceCard from '$lib/components/ServiceCard.svelte';

Summary

Components are .svelte files that encapsulate markup, logic, and styles. Import them with the $lib alias and use them like HTML elements. Pass data with props to make them dynamic.

Key takeaways:

  • Create components in src/lib/components/
  • Import with $lib/components/ComponentName.svelte
  • Use let { prop } = $props() to receive data
  • Pass data with <Component {prop} />

Next Steps

The component receives data, but how exactly do props work? Continue with Pass Service Data with $props for a deeper look.