Skip to main content
โšก Calmops

Empty States Design: Best Practices and Implementation

Empty states are often overlooked but are crucial for user experience. They appear when there’s no content to display and can either frustrate users or guide them to take action. This guide covers how to design effective empty states.

Why Empty States Matter

Empty states happen when:

  • New user with no data yet
  • Search/filter returned no results
  • Content was deleted
  • Something went wrong
  • Account needs setup

A good empty state:

  • Explains why it’s empty
  • Guides user to next action
  • Reduces support requests
  • Creates opportunity for onboarding

Elements of a Good Empty State

Icon

Visual cue that helps users understand the context:

.empty-state-icon {
  width: 80px;
  height: 80px;
  margin: 0 auto 1.5rem;
  color: #94a3b8;
}

.empty-state-icon svg {
  width: 100%;
  height: 100%;
}
<div class="empty-state">
  <div class="empty-state-icon">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
      <path d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
    </svg>
  </div>
</div>

Title

Clear, concise explanation:

.empty-state-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: #1e293b;
  margin-bottom: 0.5rem;
  text-align: center;
}

Description

More detailed explanation and context:

.empty-state-description {
  color: #64748b;
  text-align: center;
  max-width: 400px;
  margin: 0 auto 1.5rem;
  line-height: 1.6;
}

Action Button

Primary call to action:

.empty-state-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.btn-primary {
  background: #2563eb;
  color: white;
  padding: 0.75rem 1.5rem;
  border-radius: 8px;
  font-weight: 500;
  transition: background 0.2s;
}

.btn-primary:hover {
  background: #1d4ed8;
}

Empty State Types

1. New User (No Data)

Help users get started:

<div class="empty-state">
  <div class="empty-state-icon">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
      <path d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
    </svg>
  </div>
  <h3 class="empty-state-title">Welcome to Your Dashboard</h3>
  <p class="empty-state-description">
    Get started by creating your first project. Projects help you organize your work and collaborate with your team.
  </p>
  <div class="empty-state-actions">
    <button class="btn btn-primary">Create Your First Project</button>
  </div>
</div>

2. No Search Results

Help users find what they’re looking for:

<div class="empty-state">
  <div class="empty-state-icon">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
      <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
    </svg>
  </div>
  <h3 class="empty-state-title">No results found</h3>
  <p class="empty-state-description">
    We couldn't find any items matching "search term". Try adjusting your filters or search for something else.
  </p>
  <div class="empty-state-actions">
    <button class="btn btn-secondary" onclick="clearFilters()">Clear Filters</button>
    <button class="btn btn-primary" onclick="showSuggestions()">View Suggestions</button>
  </div>
</div>

3. No Items in List/Table

Guide to add content:

<div class="empty-state">
  <div class="empty-state-icon">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
      <path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
    </svg>
  </div>
  <h3 class="empty-state-title">No tasks yet</h3>
  <p class="empty-state-description">
    Create your first task to start tracking your work and stay organized.
  </p>
  <div class="empty-state-actions">
    <button class="btn btn-primary">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path d="M12 4v16m8-8H4" />
      </svg>
      Add Task
    </button>
  </div>
</div>

4. Content Deleted

Confirm deletion was successful:

<div class="empty-state">
  <div class="empty-state-icon success">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
      <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
    </svg>
  </div>
  <h3 class="empty-state-title">Item deleted</h3>
  <p class="empty-state-description">
    The item has been successfully removed. You can continue working with other items.
  </p>
  <div class="empty-state-actions">
    <button class="btn btn-secondary" onclick="undo()">Undo</button>
  </div>
</div>

5. Error/Connection Issue

Help users recover:

<div class="empty-state error">
  <div class="empty-state-icon">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
      <path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
    </svg>
  </div>
  <h3 class="empty-state-title">Unable to load data</h3>
  <p class="empty-state-description">
    We couldn't connect to the server. Please check your internet connection and try again.
  </p>
  <div class="empty-state-actions">
    <button class="btn btn-primary" onclick="retry()">
      Try Again
    </button>
  </div>
</div>

Complete CSS Implementation

/* Empty State Container */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 3rem 1.5rem;
  text-align: center;
}

/* Icon */
.empty-state-icon {
  width: 80px;
  height: 80px;
  margin-bottom: 1.5rem;
  color: #94a3b8;
}

.empty-state-icon.success { color: #16a34a; }
.empty-state-icon.error { color: #dc2626; }

.empty-state-icon svg {
  width: 100%;
  height: 100%;
}

/* Title */
.empty-state-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: #1e293b;
  margin: 0 0 0.5rem;
}

/* Description */
.empty-state-description {
  color: #64748b;
  max-width: 400px;
  margin: 0 0 1.5rem;
  line-height: 1.6;
}

/* Actions */
.empty-state-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  justify-content: center;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.625rem 1.25rem;
  font-size: 0.9375rem;
  font-weight: 500;
  border-radius: 8px;
  border: none;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-primary {
  background: #2563eb;
  color: white;
}

.btn-primary:hover {
  background: #1d4ed8;
}

.btn-secondary {
  background: #f1f5f9;
  color: #475569;
}

.btn-secondary:hover {
  background: #e2e8f0;
}

/* Subtle variant */
.empty-state-subtle {
  padding: 2rem;
  background: #f8fafc;
  border-radius: 12px;
}

Responsive Empty States

@media (max-width: 640px) {
  .empty-state {
    padding: 2rem 1rem;
  }
  
  .empty-state-icon {
    width: 60px;
    height: 60px;
  }
  
  .empty-state-title {
    font-size: 1.125rem;
  }
  
  .empty-state-actions {
    flex-direction: column;
    width: 100%;
  }
  
  .btn {
    width: 100%;
    justify-content: center;
  }
}

Animated Empty States

/* Subtle bounce for icons */
.empty-state-icon {
  animation: float 3s ease-in-out infinite;
}

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}

/* For "no results" - subtle shake */
.empty-state-icon.sad {
  animation: shake 0.5s ease-in-out;
}

@keyframes shake {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: rotate(-5deg); }
  75% { transform: rotate(5deg); }
}

JavaScript Implementation

// Generic empty state component
function EmptyState({ type, title, description, action, onAction }) {
  const icons = {
    newUser: `<svg>...</svg>`,
    noResults: `<svg>...</svg>`,
    noItems: `<svg>...</svg>`,
    deleted: `<svg>...</svg>`,
    error: `<svg>...</svg>`
  };
  
  return `
    <div class="empty-state">
      <div class="empty-state-icon">
        ${icons[type] || icons.noItems}
      </div>
      <h3 class="empty-state-title">${title}</h3>
      <p class="empty-state-description">${description}</p>
      ${action ? `
        <div class="empty-state-actions">
          <button class="btn btn-primary" data-action="${action}">
            ${action.label}
          </button>
        </div>
      ` : ''}
    </div>
  `;
}

// Usage
const container = document.getElementById('content');
if (data.length === 0) {
  container.innerHTML = EmptyState({
    type: 'noItems',
    title: 'No items yet',
    description: 'Start by adding your first item.',
    action: { label: 'Add Item', onClick: () => showAddForm() }
  });
}

Best Practices

Do

  • Explain why - Help users understand the empty state
  • Provide action - Always include a way forward
  • Match tone - Match empty state to context
  • Use visuals - Icons help recognition
  • Test edge cases - Consider all empty scenarios

Don’t

  • Don’t blame users - “You have no items” vs “No items found”
  • Don’t leave blank - Always provide guidance
  • Don’t overcomplicate - Keep it simple
  • Don’t forget mobile - Test on small screens
  • Don’t be repetitive - Different empty states for different situations

Summary

Empty states are opportunities to:

  • Onboard new users - Guide first actions
  • Recover from errors - Help users retry
  • Confirm actions - Show deletion success
  • Improve search - Help find alternatives

Key elements:

  1. Clear icon
  2. Concise title
  3. Helpful description
  4. Action button(s)
  5. Consistent styling

Design empty states with the same care as other UI elements - they’re often a user’s first impression!

Comments