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:

  1. Explain the situation — Why is it empty?
  2. Offer next steps — What can the user do?
  3. 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 === 0 for 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.