Make It Look Good
The ServiceCard works, but it’s plain. Users judge quality by appearance. A well-styled card communicates professionalism and makes the service offerings more appealing.
What You’ll Learn
- Structure CSS in Svelte components
- Create visual hierarchy with typography
- Add spacing and layout
- Design an attractive card component
Start with Structure
First, ensure the HTML is semantic:
<!-- filename: src/lib/components/ServiceCard.svelte -->
<script>
let { service, selected = false } = $props()
</script>
<article class="card" class:selected>
<div class="card-image">
<img src={service.image} alt={service.name} />
</div>
<div class="card-content">
<h3 class="card-title">
<a href="/services/{service.slug}">{service.name}</a>
</h3>
<p class="card-description">{service.description}</p>
<div class="card-meta">
<span class="price">${service.price}</span>
<span class="duration">{service.duration} min</span>
</div>
</div>
</article> Clear class names make styling straightforward.
Base Card Styling
The Card is just raw HTML now. Let’s add base styles for the card container:
<!-- filename: src/lib/components/ServiceCard.svelte -->
<!-- Script ... -->
<!-- HTML ... -->
<style>
.card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
transition: all 0.2s ease;
}
.card:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style> This creates:
- Clean white background
- Subtle border
- Rounded corners
- Smooth hover effect
Style the Image
next add styles for the image section:
<!-- filename: src/lib/components/ServiceCard.svelte -->
<!-- Script ... -->
<!-- HTML ... -->
<style>
/* ... previous styles */
.card-image {
aspect-ratio: 16 / 9;
overflow: hidden;
background: #f3f4f6;
}
.card-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.card:hover .card-image img {
transform: scale(1.05);
}
</style> Features:
- Consistent aspect ratio
- Images scale on hover
- Fallback background color while loading
Content Area Styling
next step will be to add style for the text content:
<!-- filename: src/lib/components/ServiceCard.svelte -->
<!-- Script ... -->
<!-- HTML ... -->
<style>
/* ... previous styles */
.card-content {
padding: 1.25rem;
}
.card-title {
margin: 0 0 0.5rem 0;
font-size: 1.125rem;
font-weight: 600;
line-height: 1.4;
}
.card-title a {
color: #1f2937;
text-decoration: none;
}
.card-title a:hover {
color: #3b82f6;
}
.card-description {
margin: 0 0 1rem 0;
color: #6b7280;
font-size: 0.9375rem;
line-height: 1.6;
}
</style> Visual hierarchy:
- Title is larger and bolder
- Description is smaller and gray
- Comfortable line height for readability
Meta Information
THan add style for the price and duration:
<!-- filename: src/lib/components/ServiceCard.svelte -->
<!-- Script ... -->
<!-- HTML ... -->
<style>
/* ... previous styles */
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid #f3f4f6;
}
.price {
font-size: 1.25rem;
font-weight: 700;
color: #3b82f6;
}
.duration {
font-size: 0.875rem;
color: #9ca3af;
}
</style> Selected State
Finally add style when the card is selected (like in a form):
<!-- filename: src/lib/components/ServiceCard.svelte -->
<!-- Script ... -->
<!-- HTML ... -->
<style>
/* ... previous styles */
.card.selected {
border-color: #3b82f6;
background: #eff6ff;
}
.card.selected .card-title a {
color: #1d4ed8;
}
</style> The blue tint shows selection clearly.
The Complete Styled Component
Here is the full styled ServiceCard component:
<!-- filename: src/lib/components/ServiceCard.svelte -->
<script>
let { service, selected = false } = $props()
</script>
<article class="card" class:selected>
{#if service.image}
<div class="card-image">
<img src={service.image} alt={service.name} />
</div>
{/if}
<div class="card-content">
<h3 class="card-title">
<a href="/services/{service.slug}">{service.name}</a>
</h3>
<p class="card-description">{service.description}</p>
<div class="card-meta">
<span class="price">${service.price}</span>
<span class="duration">{service.duration} min</span>
</div>
</div>
</article>
<style>
.card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
transition: all 0.2s ease;
}
.card:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card.selected {
border-color: #3b82f6;
background: #eff6ff;
}
.card-image {
aspect-ratio: 16 / 9;
overflow: hidden;
background: #f3f4f6;
}
.card-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.card:hover .card-image img {
transform: scale(1.05);
}
.card-content {
padding: 1.25rem;
}
.card-title {
margin: 0 0 0.5rem 0;
font-size: 1.125rem;
font-weight: 600;
line-height: 1.4;
}
.card-title a {
color: #1f2937;
text-decoration: none;
}
.card-title a:hover {
color: #3b82f6;
}
.card.selected .card-title a {
color: #1d4ed8;
}
.card-description {
margin: 0 0 1rem 0;
color: #6b7280;
font-size: 0.9375rem;
line-height: 1.6;
}
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid #f3f4f6;
}
.price {
font-size: 1.25rem;
font-weight: 700;
color: #3b82f6;
}
.duration {
font-size: 0.875rem;
color: #9ca3af;
}
</style> Display in a Grid
On the services page, arrange cards in a grid:
<!-- filename: src/routes/services/+page.svelte -->
<script>
import ServiceCard from '$lib/components/ServiceCard.svelte'
const services = [
/* ... */
]
</script>
<h1>Our Services</h1>
<div class="services-grid">
{#each services as service (service.id)}
<ServiceCard {service} />
{/each}
</div>
<style>
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
</style> This creates a responsive grid that:
- Shows multiple cards per row on wide screens
- Stacks to single column on mobile
- Maintains consistent spacing
Design Principles Applied
Visual Hierarchy:
- Title is prominent (size, weight)
- Description is secondary (smaller, gray)
- Price catches the eye (color, weight)
Whitespace:
- Padding separates content from edges
- Margins separate sections
- Border-top creates visual division
Feedback:
- Hover state shows interactivity
- Selected state confirms selection
- Transitions make changes smooth
Common Mistakes
Too Many Colors
/* ❌ Too chaotic */
.price {
color: #ff6b6b;
}
.duration {
color: #4ecdc4;
}
.title {
color: #45b7d1;
}
/* ✅ Cohesive palette */
.price {
color: #3b82f6;
} /* Primary blue */
.duration {
color: #9ca3af;
} /* Neutral gray */
.title a {
color: #1f2937;
} /* Dark text */ Inconsistent Spacing
/* ❌ Random values */
.card-content {
padding: 17px;
}
.card-meta {
margin-top: 13px;
}
/* ✅ Consistent scale */
.card-content {
padding: 1.25rem;
} /* 20px */
.card-meta {
margin-top: 1rem;
} /* 16px */ Summary
A well-styled ServiceCard uses visual hierarchy, consistent spacing, and thoughtful colors. Svelte’s scoped styles keep everything organized. The result is a professional-looking component that enhances user trust.
Key takeaways:
- Use semantic HTML for clear styling targets
- Create visual hierarchy with size and weight
- Add hover/selected states for interactivity
- Maintain consistent spacing scale
Next Steps
Static styles are good, but sometimes styles need to change dynamically. Continue with Dynamic Classes to learn conditional styling.