When There’s Nothing to Show
What happens when a user searches for services and nothing matches? Or when a category has no services yet? Showing a blank page is confusing. Empty states need clear messaging that tells users what’s happening and what they can do about it.
What You’ll Learn
- Detect empty data states
- Display helpful “no results” messages
- Provide actionable next steps
- Design context-specific empty states
The Problem
Imagine the services page with no services:
<script>
const services = []; // Empty!
</script>
<h1>Our Services</h1>
<ul>
{#each services as service}
<li>{service.name}</li>
{/each}
</ul> The page renders with just “Our Services” and an empty <ul>. Users see a blank area and wonder if something broke. Is the page loading? Is there an error? Did they do something wrong?
Add an Empty State
Using the {#if} / {:else} pattern from the previous lesson, check for empty data and show a helpful message:
<script>
const services = [];
</script>
<h1>Our Services</h1>
{#if services.length === 0}
<div class="empty-state">
<p>No services available at the moment.</p>
<p>Please check back soon!</p>
</div>
{:else}
<ul>
{#each services as service}
<li>{service.name}</li>
{/each}
</ul>
{/if} Now users see a clear message instead of emptiness.
Anatomy of a Good Empty State
A good empty state does more than say “nothing here.” It should:
- Explain the situation — Why is it empty?
- Offer next steps — What can the user do?
- Feel intentional — Designed, not broken
{#if services.length === 0}
<div class="empty-state">
<span class="empty-icon">🔍</span>
<h2>No Services Found</h2>
<p>We couldn't find any services matching your criteria.</p>
<p>Try adjusting your filters or browse all services.</p>
<a href="/services" class="button">View All Services</a>
</div>
{:else}
<!-- service list -->
{/if} The icon signals intention. The heading states the situation clearly. The description explains what happened. The button offers a way forward.
Context-Specific Messages
Different empty states need different messages. Consider the context:
Initial load (no services exist yet):
<div class="empty-state">
<h2>Coming Soon</h2>
<p>We're adding new services every week. Check back soon!</p>
</div> Search with no results:
<div class="empty-state">
<h2>No Results for "{searchQuery}"</h2>
<p>Try a different search term or browse our categories.</p>
<button onclick={() => searchQuery = ''}>Clear Search</button>
</div> Filtered category is empty:
<div class="empty-state">
<h2>No {categoryName} Services</h2>
<p>This category is currently empty.</p>
<a href="/services">Browse all services</a>
</div> User has no bookings:
<div class="empty-state">
<h2>No Bookings Yet</h2>
<p>Ready to book your first service?</p>
<a href="/services">Browse Services</a>
</div> Each message is specific to the situation and provides a relevant action.
Apply to BookIt’s Services Page
Update the services page to handle the empty case:
<!-- filename: src/routes/services/+page.svelte -->
<script>
const services = [
{
id: '1',
slug: 'lawn-mowing',
name: 'Lawn Mowing',
description: 'Professional lawn mowing service.',
price: 50,
duration: 60
},
// ... more services
];
</script>
<h1>Our Services</h1>
<p>Browse our available services and book your appointment.</p>
{#if services.length === 0}
<div class="empty-state">
<h2>No Services Available</h2>
<p>We're currently updating our service offerings.</p>
<p>Please check back soon or contact us for custom requests.</p>
<a href="/contact">Contact Us</a>
</div>
{:else}
<ul class="services-list">
{#each services as service}
<li class="service-item">
<h2><a href="/services/{service.slug}">{service.name}</a></h2>
<p>{service.description}</p>
<p><strong>${service.price}</strong> · {service.duration} minutes</p>
</li>
{/each}
</ul>
{/if} Style the Empty State
Add CSS to make empty states feel designed and intentional:
<style>
.empty-state {
text-align: center;
padding: 3rem 1rem;
background: #f8f9fa;
border-radius: 8px;
margin: 2rem 0;
}
.empty-state h2 {
margin: 0 0 0.5rem 0;
color: #333;
}
.empty-state p {
color: #666;
margin: 0.5rem 0;
}
.empty-state a,
.empty-state button {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #007bff;
color: white;
text-decoration: none;
border: none;
border-radius: 4px;
cursor: pointer;
}
.empty-state a:hover,
.empty-state button:hover {
background: #0056b3;
}
.empty-icon {
font-size: 3rem;
display: block;
margin-bottom: 1rem;
}
</style> Multiple Empty State Scenarios
A single page might have different empty state scenarios. Use {:else if} to handle them:
<script>
let services = $state([]);
let searchQuery = $state('');
let selectedCategory = $state('all');
let isLoading = $state(false);
let error = $state(null);
</script>
{#if isLoading}
<div class="loading-state">
<p>Loading services...</p>
</div>
{:else if error}
<div class="error-state">
<h2>Something Went Wrong</h2>
<p>{error.message}</p>
<button onclick={retry}>Try Again</button>
</div>
{:else if services.length === 0 && searchQuery}
<div class="empty-state">
<h2>No Results for "{searchQuery}"</h2>
<p>Try a different search term.</p>
<button onclick={() => searchQuery = ''}>Clear Search</button>
</div>
{:else if services.length === 0 && selectedCategory !== 'all'}
<div class="empty-state">
<h2>No Services in This Category</h2>
<button onclick={() => selectedCategory = 'all'}>View All</button>
</div>
{:else if services.length === 0}
<div class="empty-state">
<h2>No Services Available</h2>
<p>Check back soon!</p>
</div>
{:else}
<!-- Render services -->
{/if} Order matters — check the most specific conditions first.
Testing Empty States
To test, temporarily empty the services array:
<script>
const services = []; // Temporarily empty for testing
</script> Verify the empty state displays properly, then restore your data. Consider keeping a way to trigger empty states in development for design iteration.
Common Mistakes
Only Checking Truthiness
<!-- ❌ Empty array is truthy -->
{#if services}
{#each services as service}...{/each}
{/if}
<!-- ✅ Check length explicitly -->
{#if services.length > 0}
{#each services as service}...{/each}
{/if} Generic Messages
<!-- ❌ Not helpful -->
<p>No data.</p>
<!-- ✅ Specific and actionable -->
<p>No services found. Try adjusting your search or browse all categories.</p> Forgetting the Empty State Entirely
Many developers skip empty states during initial development. Add them from the start — they’re part of the complete user experience. An app without empty states feels unfinished.
No Call to Action
<!-- ❌ Dead end -->
<div class="empty-state">
<p>No bookings found.</p>
</div>
<!-- ✅ Gives user a path forward -->
<div class="empty-state">
<p>No bookings found.</p>
<a href="/services">Book your first service</a>
</div> Summary
Empty states deserve thoughtful design. Check for empty arrays with .length, provide clear explanations, and offer actionable next steps. Users should never see a blank void and wonder if something broke.
Key takeaways:
- Check
array.length === 0for empty states - Explain why it’s empty and what users can do
- Style empty states to feel intentional, not broken
- Provide context-specific messages for different scenarios
Next Steps
You can show, hide, and handle empty states. But what about rendering many items? Continue with Render Service List with {#each} to loop through arrays.