Skip to content

Empty State

Displayed when there is no content to show, guiding users toward taking action.

  • Zero State
  • Blank State

Overview

The Empty State component appears when there is no content to display within a section, page, or container. Far from being a dead end, a well-designed empty state is a guidance moment — it explains why nothing is here and what the user can do about it.

Empty states occur in three distinct contexts, each requiring different messaging:

  1. First-use empty state: The user has just signed up or entered a new section for the first time. No data exists yet because they haven't created any. This is an onboarding opportunity.
  2. No-results empty state: The user searched or filtered but nothing matched. The system has data, but none fits the current criteria.
  3. Error empty state: Data should exist but failed to load. This is a system failure, not a user action problem.

When to use an Empty State:

  • Lists, tables, or grids with zero items
  • Search results pages with no matches
  • Dashboard sections awaiting initial data
  • Filtered views that exclude all items
  • Inbox or notification centers with nothing unread

When NOT to use an Empty State:

  • For loading states — use a Skeleton or Spinner instead
  • For error messages that affect the whole page — use an error page template
  • For confirmations after clearing data — use a Toast or Alert

The key to effective empty states is actionability. An illustration with "Nothing here" is decoration. An illustration with "No projects yet — Create your first project" is a conversion funnel. Always include a call-to-action via a Button. Verify your empty state text meets contrast requirements using the Contrast Checker.

Variants

Empty State Variants

VariantContextKey Elements
First UseNew user, no data created yet.Welcoming illustration, headline ("Create your first project"), primary CTA button, optional secondary link to docs/tutorial.
No ResultsSearch or filter returned nothing.Illustration (magnifying glass, empty box), "No results for [query]", suggestion to adjust filters, clear-filters button.
Error / Failed LoadData exists but couldn't be fetched.Error illustration, "Something went wrong", retry button, optional support link.
Cleared / CompletedUser intentionally emptied a list (e.g., inbox zero).Positive illustration (checkmark, celebration), affirming message ("All caught up!"), no primary CTA needed.
Permission DeniedUser doesn't have access to this content.Lock illustration, "You don't have access", request-access button or admin contact info.
CompactInline empty state within a small container (sidebar panel, card).No illustration. Icon + single line of text + optional link.

Layout Variants

LayoutDescriptionUse Case
CenteredContent vertically and horizontally centered in the container.Full-page or large-section empty states.
Top-alignedContent aligned to the top of the container.Lists or tables where the empty state replaces row content.
InlineSingle line within the content flow.Sidebar panels, dropdown menus, small cards.

Properties

Empty State Properties

PropertyTypeDefaultDescription
titlestring—Headline text (e.g., "No projects yet")
descriptionstring—Supporting text explaining why and what to do
illustrationReactNode—SVG illustration or image
iconReactNode—Compact alternative to illustration
primaryAction{ label: string; onClick: () => void }—Primary CTA button
secondaryAction{ label: string; onClick: () => void }—Secondary text link or ghost button
size'sm' | 'md' | 'lg''md'Controls illustration size, text sizes, and spacing
layout'centered' | 'top' | 'inline''centered'Content alignment strategy
classNamestring—Additional CSS classes

Important: The title should be concise (5–8 words) and action-oriented when possible. "No projects yet" is better than "This section is empty" because it implies the user can create one. The description should provide context and guidance without being verbose.

Token Mappings

Design Token Mappings

Token CategoryToken ExampleEmpty State Usage
Color – Text--color-text-primaryTitle text
Color – Text Muted--color-text-tertiaryDescription text
Color – Illustration--color-neutral-300Monochrome illustration fill
Color – Accent--color-primary-500Illustration accent highlights
Color – Surface--color-surface-defaultContainer background
Typography--font-size-lg, --font-weight-semiboldTitle styling
Typography--font-size-sm, --font-weight-regularDescription styling
Spacing--space-6, --space-8Vertical gap between illustration, text, and CTA
Spacing--space-4Gap between title and description
Max Width--measure-narrow (400px)Text block max-width for readability

Illustrations should use design tokens for fill colors so they adapt to light/dark mode. Avoid hardcoded colors in SVGs — use currentColor or CSS custom properties.

States

Empty State States

StateDescriptionVisual Treatment
DefaultStatic display with no interaction.Illustration + text + CTA rendered normally.
With Loading CTAUser clicked the action button.CTA button enters loading state (spinner). Illustration and text remain static.
Transitioning OutData has been created/loaded, empty state is being replaced.Fade out with a gentle opacity transition (200–300ms). The new content fades in simultaneously.
Animated IllustrationIllustration has subtle motion (floating, pulsing).Gentle CSS animation. Must respect prefers-reduced-motion — disable all motion or use static fallback.

Transition note: When an empty state transitions to populated content (e.g., user creates their first item), avoid a jarring jump. Fade the empty state out and the first item in. A 200ms opacity transition is sufficient.

Accessibility

Accessibility Requirements

Semantic Structure:

  • Use heading elements (<h2> or <h3>, matching the page hierarchy) for the empty state title. Do not use <p> with bold styling — screen readers won't identify it as a heading.
  • Illustrations should be decorative (aria-hidden="true", role="presentation"). They don't convey information that isn't also in the text.
  • The container can use role="status" if the empty state appears dynamically (e.g., after filtering removes all results), triggering a polite screen reader announcement.

WCAG Compliance:

  • SC 1.4.3 (Contrast – Minimum): Title text requires 4.5:1 contrast. Description text (often in muted colors) must also meet this threshold — verify with Contrast Checker. Muted gray (#999) on white (#fff) is only 2.85:1 and fails.
  • SC 1.4.11 (Non-text Contrast): Illustration elements meaningful to understanding (rare, since they're decorative) need 3:1 contrast.
  • SC 2.4.6 (Headings and Labels): The title should be descriptive — "No results found" is better than "Oops!" for screen reader users scanning headings.
  • SC 2.4.7 (Focus Visible): The CTA button must have a visible focus indicator. This is inherited from your Button component.
  • SC 1.3.1 (Info and Relationships): The heading level must reflect the document hierarchy. An empty state inside a section with an <h2> should use <h3>.

Dynamic Empty States: When an empty state appears after user action (clearing filters, deleting all items), announce the change to assistive technology. Use aria-live="polite" on the container or programmatically move focus to the empty state title.

Usage Guidelines

Usage Guidelines

Do:

  • Always include a primary call-to-action. An empty state without an action is a dead end.
  • Match the tone to the context: welcoming for first-use, helpful for no-results, apologetic for errors.
  • Use illustrations that are lightweight (SVG preferred) and adapt to dark mode via design tokens.
  • Show the empty state in the same container where content would appear. Don't redirect to a separate page.
  • For "no results" states, suggest helpful alternatives: "Try adjusting your filters", "Check your spelling", or show a clear-all-filters button.

Don't:

  • Don't use generic messages like "Nothing here" without guidance on what to do next.
  • Don't use heavy raster images (PNG/JPG) for illustrations — SVGs scale better and load faster.
  • Don't hide the empty state behind a loading spinner indefinitely. If data fetch completes with zero results, show the empty state immediately.
  • Don't blame the user. "No results for your search" is fine; "You searched wrong" is hostile.
  • Don't overdesign the empty state to the point where it's more visually prominent than populated content. The goal is to guide users away from the empty state by encouraging content creation.
  • Don't use the same illustration for every empty state. Context-specific illustrations (empty inbox vs. empty project list) significantly improve comprehension.

Code Snippets

html
<!-- First-Use Empty State -->
<section class="empty-state" role="status">
  <div class="empty-state-illustration" aria-hidden="true">
    <svg viewBox="0 0 200 200"><!-- illustration SVG --></svg>
  </div>
  <h3 class="empty-state-title">Create your first project</h3>
  <p class="empty-state-description">
    Projects help you organize your work. Start by creating one and inviting your team.
  </p>
  <div class="empty-state-actions">
    <button class="btn btn-primary">New Project</button>
    <a href="/docs/getting-started" class="btn btn-ghost">Learn more</a>
  </div>
</section>

<!-- No Results Empty State -->
<section class="empty-state empty-state--compact" role="status" aria-live="polite">
  <svg class="empty-state-icon" aria-hidden="true"><!-- search icon --></svg>
  <h3 class="empty-state-title">No results for "quarterly report"</h3>
  <p class="empty-state-description">
    Try adjusting your search terms or clearing filters.
  </p>
  <button class="btn btn-secondary" onclick="clearFilters()">Clear filters</button>
</section>

<!-- Error Empty State -->
<section class="empty-state" role="alert">
  <div class="empty-state-illustration" aria-hidden="true">
    <svg viewBox="0 0 200 200"><!-- error illustration --></svg>
  </div>
  <h3 class="empty-state-title">Something went wrong</h3>
  <p class="empty-state-description">
    We couldn't load your data. Please try again or contact support.
  </p>
  <div class="empty-state-actions">
    <button class="btn btn-primary" onclick="retry()">Try again</button>
    <a href="/support" class="btn btn-ghost">Contact support</a>
  </div>
</section>
tsx
// Empty State Component
interface EmptyStateAction {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary' | 'ghost';
}

interface EmptyStateProps {
  title: string;
  description?: string;
  illustration?: React.ReactNode;
  icon?: React.ReactNode;
  primaryAction?: EmptyStateAction;
  secondaryAction?: EmptyStateAction;
  size?: 'sm' | 'md' | 'lg';
  layout?: 'centered' | 'top' | 'inline';
  role?: 'status' | 'alert';
}

function EmptyState({
  title,
  description,
  illustration,
  icon,
  primaryAction,
  secondaryAction,
  size = 'md',
  layout = 'centered',
  role = 'status',
}: EmptyStateProps) {
  return (
    <section
      className={`empty-state empty-state--${size} empty-state--${layout}`}
      role={role}
    >
      {illustration && (
        <div className="empty-state-illustration" aria-hidden="true">
          {illustration}
        </div>
      )}
      {icon && !illustration && (
        <div className="empty-state-icon" aria-hidden="true">{icon}</div>
      )}
      <h3 className="empty-state-title">{title}</h3>
      {description && (
        <p className="empty-state-description">{description}</p>
      )}
      {(primaryAction || secondaryAction) && (
        <div className="empty-state-actions">
          {primaryAction && (
            <Button variant={primaryAction.variant ?? 'primary'}
                    onClick={primaryAction.onClick}>
              {primaryAction.label}
            </Button>
          )}
          {secondaryAction && (
            <Button variant={secondaryAction.variant ?? 'ghost'}
                    onClick={secondaryAction.onClick}>
              {secondaryAction.label}
            </Button>
          )}
        </div>
      )}
    </section>
  );
}

// Usage: First-use
<EmptyState
  title="Create your first project"
  description="Projects help you organize your work. Start by creating one."
  illustration={<ProjectsIllustration />}
  primaryAction={{ label: 'New Project', onClick: () => openCreateDialog() }}
  secondaryAction={{ label: 'Learn more', onClick: () => navigate('/docs') }}
/>

// Usage: No results
<EmptyState
  title={`No results for "${query}"`}
  description="Try adjusting your search terms or clearing filters."
  icon={<SearchIcon />}
  size="sm"
  primaryAction={{ label: 'Clear filters', onClick: clearFilters, variant: 'secondary' }}
/>

Design Systems

Material Design 3 does not define a formal "Empty State" component, but provides extensive guidance on "empty conditions" in its content design documentation. MUI developers typically compose empty states from Box, Typography, Button, and custom SVG illustrations. The Material guideline recommends using the product's brand illustration style and keeping messaging instructional rather than whimsical.

Ant Design provides an Empty component with description (text or ReactNode), image (defaults to a built-in SVG), imageStyle, and a children slot for action buttons. Ant offers two built-in illustrations: Empty.PRESENTED_IMAGE_DEFAULT (detailed) and Empty.PRESENTED_IMAGE_SIMPLE (minimal outline). The Empty.PRESENTED_IMAGE_SIMPLE variant is preferred for inline contexts like Select dropdowns. Ant also supports Empty.useEmpty() to globally configure the empty state across all Ant components.

Chakra UI does not include a dedicated Empty State component. Developers compose one using VStack, Heading, Text, Image, and Button. Community templates exist but there's no official primitive.

Radix UI and Headless UI do not provide Empty State components, as they are purely presentational with no interaction patterns requiring headless abstraction.

Atlassian Design System provides EmptyState with header, description, primaryAction, secondaryAction, tertiaryAction, imageUrl, imageWidth, imageHeight, and maxImageWidth. Atlassian's three-tier action hierarchy (primary button, secondary button, tertiary link) is a thoughtful pattern worth adopting. Their guidelines mandate that every empty state must have at least one action — no dead ends.

Polaris (Shopify) provides EmptyState with heading, image, action, secondaryAction, fullWidth, and children. Polaris includes built-in empty state illustrations matching Shopify's brand. Their documentation emphasizes tone: first-use states should be "encouraging", error states should be "reassuring", and no-results states should be "helpful".

FeedbackZero StateBlank State