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}.
Add a Link to Details
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:
- Multiple service cards
- Each with its own data
- Links to detail pages
- 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.