---
name: empty-state-audit
description: Find every screen that looks broken, dead, or dishonest when there is no data yet — rows of zeros, "no results" with no way forward, fake placeholder content, and stats that expose emptiness. Use before a launch, when building a new product, or when the first users are not converting.
---
<!--
  empty-state-audit — from Toolbay
  https://toolbay.ai/product/empty-state-audit
  Free to use, modify, and share. Keep this line and others can find it too.
-->


# Empty State Audit

## Install

Save this file as `~/.claude/skills/empty-state-audit/SKILL.md`, or
`.claude/skills/empty-state-audit/SKILL.md` to scope it to one repo. Claude Code
auto-discovers it. Invoke with `/empty-state-audit` or by asking "what does my
app look like with no data?".

## Why this exists

Every product is empty first, and almost none are designed for it. Interfaces get
built against seed data, so the state every early visitor actually sees is the one
nobody looked at.

Empty is not merely unpolished. It is actively read as a signal: "0 reviews · 0
downloads · 0 sellers" tells a visitor nobody else is here, and for anything
social or transactional that is the strongest possible reason to leave. The
product works perfectly and still loses the user.

The wrong fix is as damaging as the problem. Padding an empty screen with
plausible-looking placeholder data is a lie that survives into production and gets
discovered exactly when someone was deciding whether to trust you.

**The goal is honest and inviting, not full.**

## Step 1 — Actually run it empty

Do not reason about this from the code. Get a truly empty database and look.

```
# a scratch database, migrations only, no seed
DATABASE_URL="<empty-db-url>" npm run dev
```

Walk every significant screen as a signed-out visitor, then as a brand-new signed-in
user. Two different empties, and the second is usually worse because the app has
promised something by then.

If a genuinely empty database is impractical, read the render path for each list
and find what it produces at length zero. Slower and less reliable, so prefer the
real thing.

## Step 2 — Find the zeros

```
rg -n "\.length\b" --type-add 'ui:*.{tsx,jsx,vue,svelte}' -tui
rg -n "(count|total|downloads|reviews|sales|members|followers|views)\b" -tui
rg -n "toFixed|toLocaleString|formatCount|formatNumber" -tui
```

For each number that reaches the screen, ask what it renders at zero, then classify:

- **HIDE.** A stat that is zero for everyone new should not be displayed. "0
  downloads" is worse than no downloads line at all, because it converts absence
  into a measured failure.
- **REFRAME.** "New" instead of "0 reviews". "Just listed" instead of "0 sales".
  True, and it turns a negative into a neutral.
- **KEEP.** Zero is genuinely informative: an unread badge, a cart, an error count.

The rule of thumb: **a zero that reflects YOUR product's youth should be hidden or
reframed. A zero that reflects the USER's own state should usually be kept.**

## Step 3 — Judge every empty list

For each list, table, feed, and grid, find the zero-length branch. A good empty
state answers three things in one screen: what belongs here, why it is empty, and
what to do next.

Grade each one:

- **MISSING** — renders nothing. The page looks broken or half-loaded.
- **DEAD END** — says "No results" and stops. Technically correct, useless.
- **GOOD** — explains, and offers the single most useful next action.

Search results deserve their own pass, because they carry a signal the visitor
cannot see: "No results for X" is ambiguous between *we have nothing like X* and
*we have nothing at all*. Say which. If the catalog is nearly empty, saying so
plainly is better than letting someone conclude their query was wrong.

## Step 4 — Hunt the dishonest fixes

The failure mode that costs the most trust. Search for content that looks like
data but is not:

```
rg -n "(placeholder|dummy|sample|example|lorem|coming soon|John Doe|Jane Smith)" -tui
rg -n "(testimonial|as seen|trusted by|used by)" -tui
```

Flag anything that a visitor could reasonably read as real:

- Fake avatars, names, or testimonials.
- "Trusted by" logos for companies that are not customers.
- Inflated or rounded-up counts, and the classic `Math.max(count, 47)`.
- Ghosted skeleton rows that never resolve, implying content is loading that
  does not exist.

Each of these has to go. Not be improved, go. A fabricated signal is discovered
precisely when someone is evaluating whether to trust you, and it costs more than
the empty screen ever did.

## Step 5 — Check what the empty state promises

An empty state usually contains your only call to action, so it is a conversion
surface, not a fallback. Check that its action is possible right now: do not offer
"Browse popular items" with no items, or "Invite your team" before teams exist.

Prefer the action that makes the emptiness go away, and make it the only one. Two
competing buttons on an empty screen is a decision the visitor should not have to
make.

## Step 6 — Report

```
Screens audited empty: <n>

DISHONEST                 <- remove today
  <file:line>  <fabricated thing>  -> what a visitor would wrongly believe

LOOKS BROKEN
  <file:line>  <list>  renders nothing at length 0

DEAD ENDS
  <file:line>  "<message>"  -> no action offered

ZEROS THAT SHOULD HIDE OR REFRAME
  <file:line>  "<0 reviews>"  -> hide | "New"

EMPTY STATES THAT ARE ALREADY GOOD
  <file:line>  <what it does well>
```

Lead with anything dishonest. That is the only category where the cost keeps
growing the longer it stays.

## Rules

- Run it empty. Reading the code misses what a screen composes to.
- Never pad. Show less, reframe honestly, or explain, but never invent.
- Hide zeros that measure your youth; keep zeros that describe the user.
- Every empty state gets exactly one obvious next action, and that action must
  work today.
- "No results" without context is a dead end. Say whether the shelf is empty or
  the query missed.
