How to Design Empty States in an AI-Built App
AI builders preview against seeded demo data, so the screen a real user sees first is the one nobody looked at. The four empty states, and how to get a builder to write them.
How to Design Empty States in an AI-Built App
An empty state is what your app shows when there is nothing to show, and in an app built with AI it is almost always the weakest screen in the product. The fix is to treat the empty screen as a real screen with its own copy and its own primary action, and to ask for it explicitly, because your builder will not produce one on its own.
The reason is mechanical rather than mysterious, and it is worth understanding before you start writing prompts.
Why AI builders get this wrong so consistently
When an AI builder scaffolds a list view, it generates seed data at the same time. Three fake customers, five fake invoices, a tidy little table. Every preview you see during the build is populated. Every screenshot in the chat is populated. The builder is never looking at the screen you are about to ship, because the screen you are about to ship has zero rows in it.
Then you deploy, a real person signs up, and the first thing they see is a heading, a search box that filters nothing, and a large area of white. There is no bug. Every test passes. The app is simply mute at the exact moment the user most needs direction.
This is the same category of problem as forgetting to check the app against real input, which is why it belongs in the same pass as testing an AI-built app before launch rather than in a polish phase afterwards.
The four empty states every app has
Most people think of one empty state. There are four, they mean different things, and a user who cannot tell them apart will assume the app is broken.
State | What happened | What the user needs |
|---|---|---|
First run | The account is new and no data exists yet | An explanation of what this screen will contain, and one button to create the first item |
No results | Data exists but a filter or search excluded all of it | The active filter stated plainly, and a way to clear it |
Cleared out | There was data and the user deleted or archived all of it | Confirmation that this was intentional, and a route to the archive |
Blocked | Data exists but this user lacks permission, or a fetch failed | The reason, and who to ask or what to retry |
The failure that costs you the most is collapsing the second into the first. A user searches for a customer who is not in the system, sees your cheerful first-run message saying they have no customers yet, and concludes that their data is gone. That is a support ticket and a trust problem from a single missing conditional.
What good empty state design actually does
Nielsen Norman Group's guidance on empty state interface design is the standard reference and it comes down to three jobs. Explain what belongs here. Say how to get some. Do not make the user feel they have made a mistake.
To which, for anything built quickly with AI, add a fourth: distinguish empty from broken. A spinner that resolves into nothing looks identical to a failed request. If your fetch can fail, the failed state needs different words from the empty state, or your users will refresh until they give up.
Writing the copy
Empty state copy is short enough that people write it carelessly. A pattern that holds up:
One line naming what lives here, in the user's words, not the table's. Clients, not client records.
One line on how the first one arrives, including the route that is not the button. Add one manually or import a CSV.
One primary action. If there are two equally weighted buttons, you have not decided what the user should do.
No apology, no exclamation mark, and no illustration that takes more vertical space than the instruction.
Compare these two, for a list of client intake forms:
Bad: No data found.
Better: No intake forms yet
Forms appear here once a client submits one. You can
also add a form manually while you are testing.
[ Add a form ] Or copy your intake linkThe better version tells you what the screen is for, sets the expectation that it fills up through a route you do not control, and still gives you something to press. If the app also supports bulk loading, the empty state is the correct place to mention it, which is the natural pairing with adding CSV import to an AI-built app.
A worked example
Take a client intake list. The component needs to branch four ways before it renders a table, and the order matters: check the error first, then loading, then the filter, then the genuinely empty case.
function IntakeList({ items, loading, error, query }) {
if (error) return <Blocked reason={error} onRetry={refetch} />;
if (loading) return <Skeleton rows={3} />;
if (items.length === 0 && query) {
return (
<Empty
title={`No forms match "${query}"`}
body="Try a shorter search, or clear the filter to see everything."
action={{ label: "Clear search", onClick: clearQuery }}
/>
);
}
if (items.length === 0) {
return (
<Empty
title="No intake forms yet"
body="Forms appear here once a client submits one. You can also add one manually while you are testing."
action={{ label: "Add a form", onClick: openForm }}
secondary={{ label: "Copy intake link", onClick: copyLink }}
/>
);
}
return <Table rows={items} />;
}Two details that are easy to miss. The no-results state quotes the query back, which is what tells the user the app heard them. And the skeleton comes before the empty check, so a slow connection never flashes the first-run message at someone who does have data.
Getting your builder to produce them
Builders respond well to being told the states exist, because the problem is omission rather than inability. A prompt that works:
For every list, table and search view in this app, add explicit
empty states. Handle four cases separately:
1. first run, no data has ever existed
2. no results, data exists but the current filter excludes it
3. cleared, the user deleted everything
4. blocked, the fetch failed or permission is missing
Check error, then loading, then filtered-empty, then truly empty,
in that order. Each state needs a heading, one sentence of body
copy, and at most one primary action. Do not use seeded demo data
to preview these. Render each state with an empty array.The last line does the heavy lifting. Without it the builder will confirm the states are handled while still previewing against its own seed data, and you will find out otherwise in production.
Ask it to show you each state as a separate screenshot or route before you accept the change. This is the same discipline as asking an AI coding agent to explain its plan before it edits, applied to a UI surface.
Accessibility and the empty screen
An empty state is usually the one screen with no landmarks, which makes it a common screen reader dead end. Give the container a heading that is a real heading element rather than styled text, announce loading and error transitions through a live region, and make sure the primary action is reachable by keyboard from the page's first tab stop. The broader checklist is in making an AI-built app accessible.
Frequently asked questions
Do I need an illustration in my empty states?
No. An illustration is optional decoration and it competes with the instruction for attention. If you use one, keep it small enough that the heading and the button are both visible without scrolling on a phone.
Should the empty state be the same as the onboarding flow?
They overlap but they are not the same. An onboarding flow runs once and explains the product. An empty state recurs whenever a list is empty, including three months in after someone archives everything. Build them separately and let the empty state be the shorter one.
What about a dashboard with several empty widgets?
Do not repeat the same message six times. Give the page one empty state that covers the whole dashboard when nothing has data yet, and let individual widgets show a compact zero value once any data exists.
How do I test empty states without deleting my data?
Create a second account with nothing in it and keep it. It costs nothing, it takes thirty seconds to make, and it is the only reliable way to see what a new user sees after you have been using your own app for a month.
Is this worth doing before launch?
Yes, because every single user sees the first-run state and they see it before they see anything else. It is the highest-traffic screen in the product on day one and usually the least considered.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


