Skip to main content

Empty states that convert: onboarding design patterns (2026)

Turn blank screens into activation moments with empty state design patterns. React examples, aria-live wiring, and four proven patterns.

DomiDex
DomiDexCreator of Tour Kit
April 9, 202610 min read
Share
Empty states that convert: onboarding design patterns (2026)

Empty states that convert: onboarding design patterns

Your user signs up, lands on the dashboard, and sees a white rectangle. No data. No projects. No indication of what to do first. According to conversion research from SaasFactor, roughly 75% of users abandon a product within the first week when onboarding fails to guide them past this moment. The blank screen isn't a design gap. It's a conversion leak.

Most React teams treat empty states as a ternary: data.length === 0 ? <p>No items yet</p> : <DataTable />. That covers the render path. It doesn't do anything about the user who just bounced.

This guide breaks down four empty state design patterns that turn dead screens into activation moments, with typed React examples, accessibility wiring most articles skip entirely, and a headless approach you can plug into any design system.

npm install @tourkit/core @tourkit/react

What is an empty state design pattern?

An empty state design pattern is a structured UI response for screens with no user data, covering the layout, copy, illustration, and call-to-action that guide a user toward their first meaningful interaction. Unlike loading states or error boundaries, empty states signal opportunity rather than failure. The Nielsen Norman Group identifies three forces every empty state must balance: communicating system status, providing learning cues, and offering direct task pathways.

This matters for onboarding because empty states are the only UI surface that 100% of new users encounter. Modals get dismissed. Tooltips get skipped. But the blank dashboard? Everyone sees it, and what you put there determines whether they create their first project or close the tab.

Why empty states matter more than your product tour

Product tours fire once and disappear. Empty states persist until the user takes action. They're a standing invitation. When Autopilot redesigned their empty trial experience with templated demo content, they cut their 50% free-trial churn rate dramatically (Userpilot case study). The fix wasn't a better tooltip sequence. It was giving users something to see before they'd created anything.

Here's the math that makes this concrete. If your SaaS converts 1,000 signups per month and 75% churn before activation, that's 750 users lost. At $100 average annual revenue per user, you're leaking $75,000 in potential ARR per monthly cohort. A well-designed empty state that moves activation from 25% to 40% recovers $15,000/month ($180,000/year) without touching your acquisition funnel.

Empty states also compound with other onboarding surfaces. A product tour that fires on an empty dashboard is disorienting (what is the tour pointing at?). But an empty state that guides the user to create their first item, then triggers a contextual tour on that item? That sequence sticks.

The four types of empty states

Not every blank screen is the same. The CSS-Tricks "Nine States of Design" framework counts "nothing" as one of nine states you should always design for. Within that "nothing" state, four distinct types each demand different copy, layout, and action.

First-time use: the onboarding moment

The user has no data because they just arrived. This is the highest-stakes empty state, the one where you convert or lose. The pattern: an illustration (or screenshot of the populated view), a single sentence explaining what this screen will show, and one primary CTA that starts the creation flow.

Shopify's Polaris design system calls this the "starting" state and recommends keeping copy encouraging rather than instructional. "Create your first product" outperforms "You haven't added any products yet" because the first is forward-looking and the second is a status report.

No results found: the recovery moment

Search or filter returned nothing. The user isn't new. They have data, just not matching data. The pattern: acknowledge what they searched for, suggest adjustments ("Try a broader date range"), and offer a reset action. Never show a blank container with no explanation.

Post-completion: the next-step moment

The user finished a task and the list is now empty. This is where most apps show nothing. A missed handoff. The pattern: celebrate the completion briefly ("All caught up"), then suggest the next logical action. Inbox zero is a familiar example; your app's version should point somewhere useful.

Feature education: the discovery moment

A feature exists but the user hasn't used it yet. The pattern: show what the feature does with a concrete example, and let the user try it with one click. Tour Kit's hint system works well here: a beacon on the empty section that expands into a contextual explanation.

Empty state design patterns that drive activation

Four patterns we've seen work across SaaS products, each with a different conversion mechanism.

The guided action pattern

The most common and most effective. One illustration, one sentence of context, one CTA button. The key: the CTA opens a modal, drawer, or inline form, not a new page. Keeping the user in context reduces abandonment.

// src/components/ProjectsEmptyState.tsx
import { EmptyState } from './EmptyState';

export function ProjectsEmptyState() {
  return (
    <EmptyState
      variant="first-use"
      heading="Create your first project"
      description="Projects organize your work into separate spaces with their own settings and team members."
      action={{
        label: 'New project',
        onClick: () => setCreateDialogOpen(true),
      }}
      // Optional: Tour Kit integration for contextual guidance
      tourStepId="first-project"
    />
  );
}

Demo data with context labels

Instead of a blank screen, show what the populated state will look like, with sample data clearly labeled as examples. Autopilot's templated dashboard approach falls here. The trick: never let demo data be mistakable for real data. Use a banner ("This is sample data. Create your first campaign to see real results.") and muted visual treatment.

This pattern works best for complex screens where the user needs to understand what they're building toward before they start. Analytics dashboards, project timelines, CRM pipelines.

The milestone tracker

Embed a progress indicator directly in the empty state. "Complete 3 steps to set up your workspace: 1. Add your first project, 2. Invite a teammate, 3. Connect an integration." Each completed step removes itself, and the empty state progressively reveals the real UI underneath.

Tour Kit's checklist package implements this pattern out of the box. Define tasks with dependencies, and the checklist handles completion tracking, progress calculation, and persistence.

// src/components/DashboardEmptyState.tsx
import { Checklist, ChecklistItem } from '@tourkit/checklists';

export function DashboardEmptyState() {
  return (
    <Checklist checklistId="workspace-setup">
      <ChecklistItem
        taskId="create-project"
        title="Create your first project"
        href="/projects/new"
      />
      <ChecklistItem
        taskId="invite-team"
        title="Invite a teammate"
        href="/settings/team"
      />
      <ChecklistItem
        taskId="connect-integration"
        title="Connect an integration"
        href="/integrations"
      />
    </Checklist>
  );
}

Conversational CTA

A 2026 pattern gaining traction in PLG products: the empty state presents a single routing question. "What are you building?" with two or three options that each lead to a tailored setup flow. SaaSUI's 2026 onboarding research documents this as the next evolution: asking a single routing question at signup that reshapes every downstream empty state the user encounters.

Building accessible empty states in React

Accessibility is the gap every empty state article ignores. The Smashing Magazine 2017 reference that most UX articles still link to doesn't mention aria-live once. WCAG 4.1.3 (Level AA) requires that status messages be programmatically determinable by assistive technologies without receiving focus. An empty state transition is exactly that kind of status message.

The three-state handling order

Check states in this sequence to avoid impossible UI combos. As Iva Kop explains on LogRocket, "It's up to developers to make designs come to life, which, of course, means taking into account all possible states."

// src/hooks/useDataState.ts
type DataState<T> =
  | { status: 'loading' }
  | { status: 'error'; error: Error }
  | { status: 'empty' }
  | { status: 'populated'; data: T[] };

function deriveState<T>(
  isLoading: boolean,
  isError: boolean,
  error: Error | null,
  data: T[] | undefined
): DataState<T> {
  // Order matters: error > loading > empty > populated
  if (isError && error) return { status: 'error', error };
  if (isLoading && !data) return { status: 'loading' };
  if (!data || data.length === 0) return { status: 'empty' };
  return { status: 'populated', data };
}

aria-live and screen reader announcements

When your UI transitions from loading to empty, screen reader users hear nothing unless you wire up a live region. MDN's aria-live documentation specifies that the live region must be empty on initial render. Only populate it when the state changes.

// src/components/EmptyState.tsx
import { useRef, useEffect, useState } from 'react';

interface EmptyStateProps {
  variant: 'first-use' | 'no-results' | 'post-completion' | 'feature-education';
  heading: string;
  description: string;
  action?: { label: string; onClick: () => void };
}

export function EmptyState({ variant, heading, description, action }: EmptyStateProps) {
  const [announced, setAnnounced] = useState(false);

  useEffect(() => {
    // Delay announcement so live region starts empty
    const timer = setTimeout(() => setAnnounced(true), 100);
    return () => clearTimeout(timer);
  }, []);

  return (
    <section
      role="region"
      aria-label={heading}
      className="flex flex-col items-center gap-4 py-16 text-center"
    >
      <h2 className="text-lg font-semibold">{heading}</h2>
      <p className="max-w-md text-muted-foreground">{description}</p>
      {action && (
        <button
          onClick={action.onClick}
          className="rounded-md bg-primary px-4 py-2 text-primary-foreground"
        >
          {action.label}
        </button>
      )}
      {/* Screen reader announcement */}
      <div
        role="status"
        aria-live="polite"
        aria-atomic="true"
        className="sr-only"
      >
        {announced ? `${heading}. ${description}` : ''}
      </div>
    </section>
  );
}

Headless empty state with Tour Kit

Tour Kit's headless architecture means you separate detection logic from rendering. The useTour hook can trigger contextual guidance when empty states appear, without coupling your empty state component to any specific UI library.

// src/components/HeadlessEmptyState.tsx
import { useTour } from '@tourkit/react';

export function ProjectsDashboard({ projects }: { projects: Project[] }) {
  const { startTour } = useTour();

  if (projects.length === 0) {
    return (
      <EmptyState
        variant="first-use"
        heading="Create your first project"
        description="Projects organize your tasks, docs, and team access."
        action={{
          label: 'New project',
          onClick: () => {
            setCreateDialogOpen(true);
            // Fire a contextual tour after the dialog opens
            startTour('first-project-tour');
          },
        }}
      />
    );
  }

  return <ProjectGrid projects={projects} />;
}

This pattern keeps Tour Kit out of the empty state component itself. The empty state handles layout and copy; Tour Kit handles the guided flow that fires after the user takes the first action. Clean separation, testable independently.

Common mistakes that kill empty state conversion

Loading skeleton that resolves to nothing. If data might be empty, transition directly to the empty state after fetching. Don't render a skeleton that ends with a blank container.

"No data" as your entire empty state. Two words and no action. Every empty state needs: what this screen is for, why it's empty, and what to do about it.

Linking to docs instead of triggering the action. "Learn how to create a project" sends users away. "Create a project" keeps them in the app. The CTA should start the workflow, not explain it.

One generic empty state for every screen. The analytics empty state should preview what metrics will appear. The team empty state should show how collaboration works. Generic "Nothing here yet" wastes your best onboarding real estate.

Forgetting filter/search empty states. A user who filters 50 items to zero needs a different message than a first-time user. "No projects match 'mobile'" with a "Clear filters" button beats the onboarding CTA.

Tools and libraries for empty state onboarding

ToolApproachEmpty state supportBest for
Tour KitHeadless React libraryComposable with any empty state component via hooks + checklistsTeams with existing design systems who want logic without UI opinions
AppcuesNo-code SaaS overlayModals and tooltips over empty states, no native empty state componentNon-technical teams needing quick visual overlays
Shopify PolarisDesign system componentFirst-class EmptyState component with illustration slotShopify app developers
Carbon Design SystemEnterprise design systemDocumented empty state pattern with three variantsIBM ecosystem and enterprise apps
UserpilotNo-code SaaSChecklists that overlay empty screens; no in-page integrationProduct teams without frontend resources

Tour Kit doesn't ship an EmptyState component, and that's intentional. Your empty state should match your design system exactly, not inherit styles from a library. What Tour Kit provides is the contextual layer: hints that draw attention to the CTA, checklists that track onboarding progress, and tours that guide users through the action once they click. The library is React 18+ only and doesn't include a visual builder, so you'll need React developers on the team. For a detailed component implementation, see our React empty state component tutorial.

We built Tour Kit, so take the comparison above with appropriate skepticism. Every claim is verifiable against the respective docs.

FAQ

What is the best empty state design pattern for SaaS onboarding?

The guided action pattern (a single illustration, one sentence of context, and one primary CTA) converts most reliably for first-time users encountering an empty state design pattern during onboarding. Shopify Polaris and IBM Carbon both standardize on this structure. Pair it with a Tour Kit checklist for multi-step flows where the empty state tracks progress across setup tasks.

How do you make empty states accessible with screen readers?

Use aria-live="polite" with role="status" on a container that starts empty and gets populated when the UI transitions to the empty state. WCAG Success Criterion 4.1.3 (Level AA) requires status messages to be programmatically determinable without receiving focus. Set aria-atomic="true" so the full message is announced, not just changed fragments.

Should you show demo data or an empty state?

Use demo data when the populated view is complex and the user needs to understand what they're building toward (analytics dashboards, project timelines, CRM pipelines). Use a guided empty state when the first action is simple and self-explanatory: creating a project, uploading a file. Some products combine both: showing labeled sample data with a banner explaining it's not real.

How do empty states work with product tours?

Empty states and product tours serve different moments. The empty state guides users to create their first item. The product tour fires after that item exists, walking through features on real data. Tour Kit's useTour hook lets you trigger a tour from the empty state's CTA handler, connecting the two without coupling them.

What is the biggest empty state mistake in React apps?

Rendering a data container that resolves to an empty list. The data.map(item => ...) pattern returns nothing when the array is empty, so users see a header, filter controls, and blank space. Always check data.length === 0 explicitly and render a purpose-built empty state with guidance and a CTA.


Ready to try userTourKit?

$ pnpm add @tour-kit/react