All articles
Design & UXOctober 23, 2026 6 min read

Empty States Are the Feature You Shipped Without Designing

Empty states are the first screen most users actually see. Here's how to design them so they teach, convert, and don't make your product feel broken on day one.

Empty States Are the Feature You Shipped Without Designing

Every product has a first screen, and for most users it's empty. No projects, no messages, no transactions, no history. If you treat that screen as an edge case, you've just shipped onboarding written by nobody.

Empty states are not a decoration problem. They're the moment a user decides whether your product is a tool or a toy they'll uninstall by Thursday. We've rebuilt enough dashboards to know the pattern: the teams that take empty states seriously see noticeably better activation; the teams that drop in a sad cloud SVG wonder why trial-to-paid is flat.

Why most empty states fail

The default empty state is some variant of: a centered icon, the words "No items yet", and maybe a button labelled "Create". It's technically correct and functionally useless. It tells the user what they already know (there's nothing here) without answering the only two questions that matter:

  1. What is this screen for when it's full?
  2. What's the smallest thing I can do right now to make progress?

Skip either of those and you've built a dead end. The user bounces back to the sidebar, clicks something else, and starts building a mental model of your product as "a bunch of empty rooms".

The three kinds of empty you're conflating

Most design systems have one empty state component. That's the first mistake. There are at least three distinct scenarios, and they want different content:

  • First-run empty — the user has never created anything. They need education and a path.
  • User-cleared empty — they had items and deleted or archived them all. They need reassurance and a way back.
  • Filtered empty — the data exists but the current filter hides it. They need to know why it looks empty and how to recover.

Showing "Create your first project" to a user who just archived their last project is condescending. Showing "No results match your filters" to a brand-new account is terrifying — results of what?

The anatomy of an empty state that works

After enough rounds of this across SaaS, marketplace, and internal-tool projects, we've landed on a reliable structure. Not a template to copy pixel-for-pixel, but a checklist of what the screen owes the user.

  • A headline that describes the end state, not the current one. "Your pipeline lives here" beats "No deals yet".
  • One sentence of context. What goes in this view, who adds it, when.
  • A primary action that is the smallest useful first step. Not "Set up your workspace" — "Add your first contact".
  • A secondary action for the curious. Import sample data, watch a 40-second demo, read the doc.
  • A visual that implies the filled state. A faint sketch of what the real UI looks like, not a mascot shrugging.

That last point does a lot of heavy lifting. If a user can see the shape of the thing they're building toward, the empty state stops feeling like absence and starts feeling like a scaffold.

Sample data is underrated

One of the highest-leverage changes we make on client projects: offer sample data on the first-run empty state. Not auto-populated — a button. "See it with sample data" lets the user explore the real UI without the commitment of inputting their own. When they're ready, a single "Clear sample data" action resets them.

This works especially well for anything with graphs, boards, or lists where the shape of a populated view is the actual sales pitch.

A component pattern that scales

Here's the rough shape of an empty-state component we've used across React projects. The point isn't the exact API — it's that variant is a first-class prop, because the three empty types are not interchangeable.

type EmptyStateProps = {
  variant: 'first-run' | 'cleared' | 'filtered';
  title: string;
  description: string;
  primaryAction?: { label: string; onClick: () => void };
  secondaryAction?: { label: string; onClick: () => void };
  illustration?: React.ReactNode;
};

export function EmptyState({
  variant,
  title,
  description,
  primaryAction,
  secondaryAction,
  illustration,
}: EmptyStateProps) {
  return (
    <div
      role="status"
      aria-live="polite"
      className="flex flex-col items-center text-center max-w-md mx-auto py-16"
    >
      {illustration && (
        <div aria-hidden="true" className="mb-6 opacity-80">
          {illustration}
        </div>
      )}
      <h2 className="text-xl font-semibold mb-2">{title}</h2>
      <p className="text-sm text-neutral-600 mb-6">{description}</p>
      <div className="flex gap-3">
        {primaryAction && (
          <button
            onClick={primaryAction.onClick}
            className="px-4 py-2 rounded-md bg-neutral-900 text-white"
          >
            {primaryAction.label}
          </button>
        )}
        {secondaryAction && (
          <button
            onClick={secondaryAction.onClick}
            className="px-4 py-2 rounded-md border border-neutral-300"
          >
            {secondaryAction.label}
          </button>
        )}
      </div>
    </div>
  );
}

Two things worth calling out. role="status" and aria-live="polite" matter because empty states often appear after an async fetch — screen reader users need to know the list resolved to zero, not that it's still loading. And the illustration is aria-hidden, because describing a decorative sketch wastes everyone's time.

Where this breaks down

One component can't carry the whole load. For complex surfaces — a Kanban board, a calendar, an analytics dashboard — a centered card looks lost. In those cases, we render the empty state inside the chrome of the real UI: the columns are visible but each contains a soft placeholder; the calendar grid renders with a single CTA in the current week.

That preserves the spatial model. The user learns the layout while they're still empty, so when data arrives they already know where it'll land.

Writing the copy without sounding like a chatbot

Empty-state copy is where product personality shows up or dies. A few rules that have held up:

  • Describe the destination, not the void. "Track every candidate here" > "No candidates".
  • Second person, present tense. "Add a candidate" not "Candidates can be added".
  • One sentence of context, not a paragraph. If you need three sentences to explain what a view does, the view's name is wrong.
  • Skip the jokes on the second screen. A witty empty state on the dashboard is charming. The same tone on every zero-data view across the app reads as nervous.
  • Never apologize. "Oops, nothing here!" implies something broke. Nothing broke. The user just hasn't acted yet.

For the filtered-empty variant specifically, name the filters that are hiding results and offer a one-click clear. "No invoices match status: overdue and client: Acme. Clear filters." That's a bug report the UI writes to itself.

Measuring whether it actually works

Empty states are measurable, which is why it's strange how often they ship on vibes alone. The metrics we instrument on first-run empty states:

  • Click-through on the primary action — if it's below the rate at which users leave the screen entirely, the CTA is wrong.
  • Time to first meaningful action — how long between landing on the empty state and the first created record.
  • Sample-data adoption — if offered, what percentage try it, and do those users convert at a higher rate?
  • Return visits to the empty state — a user who sees the same empty state three days in a row is a user you're about to lose.

Tag the events with the variant. First-run and filtered-empty will have wildly different healthy baselines, and averaging them hides both.

Where we'd start

If you're staring at your product and realising half the zero-data screens are generic, don't try to redesign all of them this sprint. Pick the one empty state new users hit first — usually the main dashboard or the primary object list — and rebuild just that one. Give it the destination-headline, the sample-data option, and proper event tracking. Ship it, watch the activation funnel for two weeks, then move to the next.

If you want a hand threading this through an existing design system without blowing up your component library, our product and design team does exactly this kind of surgical UX work. Otherwise: open your app in an incognito window, create a fresh account, and screenshot every empty screen you see. That folder is your backlog.

#UX#Design Systems#Onboarding#Conversion

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project