The Screen Nobody Designed (Until It Was Too Late)
I want to tell you about a screen that cost a client real users — a screen that wasn't broken, didn't throw errors, and looked completely fine on a Figma artboard. It was the empty state on their main dashboard, and for months it was the first thing new users saw after completing registration.
The screen showed a table with no rows. A heading that said "Your Reports." A sidebar. A top navigation. Everything in the right place — except any reason for the user to stay.
I only caught it because I was reviewing session recordings in Hotjar trying to diagnose churn on a B2B SaaS tool I'd built for a logistics company. We had decent sign-up numbers but terrible week-two retention. Users came in, looked around, and didn't come back. The recordings were uncomfortable to watch. You could see the cursor moving to different parts of the screen, hesitating, then the tab just... closing.
Nobody panicked. Nobody raged. They just left quietly, which is the worst kind of churn to diagnose.
What the "Before" Actually Looked Like
The dashboard was the first screen after onboarding. Users had just set up their account, confirmed their email, filled out a short profile — and then landed here.
The before state was something like this:
- A page title: "Your Reports"
- A table with five column headers: Date, Type, Status, Created By, Actions
- Zero rows in the table body
- A small pagination footer showing "Showing 0–0 of 0 results"
- A "+ New Report" button in the top-right corner of the page header
That's it. The button existed, technically. But it was sitting in the corner of a page full of empty columns and a pagination line that cheerfully announced "0 of 0 results." The cognitive message the UI was sending was: nothing is here yet, and this is where nothing lives.
The button was invisible — not literally, but functionally. Users weren't ignoring it because they were careless. They were ignoring it because the page around it communicated emptiness so thoroughly that the button didn't register as a call to action. It registered as part of the furniture.
Diagnosing the Real Problem
I've written about similar invisible-but-damaging UX decisions before — the kind of thing that looks fine in isolation but quietly bleeds users. The pattern here wasn't unique. Empty states almost always fail for the same cluster of reasons:
- They explain nothing. "Your Reports" doesn't tell a new user what a report is, what it contains, or why they'd want one.
- They emphasize the void. Showing column headers with zero rows, plus a "0 of 0" count, makes the emptiness feel like a state of failure rather than a state of readiness.
- The CTA is positionally isolated. A button in the top-right corner of a page is a power-user affordance. Someone who already knows what they're doing uses that button. A confused new user's eyes are in the center of the page, looking at blank space.
- There's no emotional reassurance. New users are anxious. They've just committed to using something new. An empty screen with no guidance reads as: "You're on your own."
The technical implementation was fine. The PHP query was working correctly — it returned zero rows because the user hadn't created anything yet. MySQL was doing its job. The problem was entirely in what we chose to render when that result came back empty.
The After: What We Built Instead
The redesign replaced the empty table entirely when row count was zero. Here's the logic flow in rough terms: if the query returns no results and this is the user's first session, we render the empty state component instead of the table structure. If there are results, the table renders normally.
The empty state component itself had five distinct parts:
1. A Purposeful Illustration
Not stock art, not a generic icon. A small custom SVG illustration — a simplified document with a plus symbol — that looked intentional and matched our design system's stroke weight and color palette. It was centered, about 120px tall, with generous padding above and below. The goal was to make the page feel designed, not empty by accident.
2. A Headline That Reframes the Situation
Before: nothing.
After: "Create your first report to get started."
That's a small change in word count but a massive change in meaning. It names the action, it positions this as a beginning rather than a gap, and it sets an expectation: there is a next step, and it's obvious.
3. A Two-Sentence Description
"Reports let you summarize shipment activity by date range, route, or carrier. You can export them as PDF or share a live link with your team."
This answered the "wait, what even is this?" question that we realized new users had but never asked out loud. It also seeded a value proposition — PDF export, shareable links — that we knew from sales calls were high-interest features. The empty state became a mini pitch for the feature it was gatekeeping.
4. A Large, Centered Primary CTA
Same "+ New Report" functionality as before, but now it lived in the center of the screen, inside the empty state component, styled as a full primary button — not a ghost button, not an icon-only affordance. Solid background, high contrast, generous padding. Impossible to miss.
5. A Secondary Helper Link
Below the primary button: a small text link — "See how reports work ?" — that opened a short two-minute walkthrough. This was for users who weren't ready to commit but needed more confidence before acting. We tracked clicks on this separately and it had a surprisingly high click rate in the first two weeks after launch.
The Conditional Rendering in PHP
On the implementation side, the logic was straightforward. I was already fetching the row count as part of the existing query, so rendering the right component was a matter of checking that count before deciding what to output:
<?php if ($totalReports === 0 && $isFirstSession): ?>
<div class="empty-state">
<?php include 'partials/empty-report.php'; ?>
</div>
<?php else: ?>
<table class="report-table">
<!-- normal table render -->
</table>
<?php endif; ?>
The $isFirstSession flag was set in the session on first login and cleared after 48 hours or after the user created their first report — whichever came first. This was intentional: returning users who had deleted everything back to zero would see a slightly different empty state that didn't treat them like they'd never used the product before. That distinction mattered for tone.
I also removed the pagination footer entirely from the zero-state render. There's no reason to tell someone "0 of 0 results" when you're already showing them a dedicated empty state component. The number just undermines the message.
What Changed After Launch
We didn't have a formal A/B test running — this was a small client project on shared hosting, not a product with tens of thousands of MAU. But we had Hotjar session recordings and we tracked the specific event of clicking "+ New Report" within the first session.
In the two weeks before the redesign, roughly 18% of new users clicked the new report button on their first session. In the two weeks after, that number moved to 41%. Not a controlled study, not statistically ironclad — but directionally clear and consistent with what the session recordings showed. Users weren't hesitating on the empty state anymore. They read it, they clicked.
Week-two retention also improved, though I'm more cautious about attributing that entirely to the empty state change since we made a few other small fixes around the same time. But the first-session activation metric was clean enough to point at this redesign specifically.
What This Taught Me About Empty States as a Design Category
Empty states aren't edge cases. For a significant portion of your users — everyone who signs up and hasn't done anything yet — the empty state is the product. It's the first real interaction they have after onboarding. Treating it as a fallback render, something the table component handles when there's nothing to show, is designing with the wrong mental model entirely.
The better mental model: an empty state is a landing page for a feature. It needs to sell the feature, guide the user into it, and make the next action feel achievable rather than arbitrary. If you've ever written a decent product landing page, you already have the instincts for this. Apply them here.
A few principles I now apply consistently:
- Never render a table shell with zero rows. If there's no data, remove the table entirely. The header columns are meaningless without content to explain them.
- Always name the action, not just the state. "No reports yet" is worse than "Create your first report." One describes; the other directs.
- Center your CTA in the empty state, not the page chrome. The button needs to live where the user's attention already is — which, on a blank screen, is the middle.
- Write the two-sentence description even if it feels redundant. Your power users will skip it. New users need it.
- Distinguish between true first-time empty and "you deleted everything" empty. The tone should be different. One is an invitation; the other is a neutral acknowledgment.
It's also worth noting that this problem compounds with other UX gaps. If your onboarding sequence doesn't quite land — and I've written about how easily that can happen in The Onboarding Flow That Silently Bled Users — then your empty state becomes the last chance to recover a confused user before they churn. That's a lot of weight to put on one screen. Design it like it matters, because it does.
The Takeaway Worth Keeping
The fix wasn't technically hard. It wasn't a new feature, a database change, or a major refactor. It was a decision to take the zero-data state seriously — to treat it as a designed experience rather than an absence of one. The before version was a product that shrugged at new users. The after version met them where they were and told them exactly what to do next.
Go look at your own empty states right now. Not the happy path. Not the dashboard with real data. Open an incognito window, sign up fresh, and look at what your new users actually see. I'd bet at least one of those screens is shrugging at them right now.
Frequently Asked Questions
What should an empty state always include at minimum?
At minimum, an empty state needs to explain why it's empty, what the user can do next, and a direct action to take — ideally a primary CTA button. Without all three, users often assume something is broken or that the product doesn't work yet.
Is it worth designing custom empty states for every section of an app?
Yes, especially for high-traffic screens like dashboards, activity feeds, and data tables. A generic "No data found" message works as a fallback, but section-specific empty states dramatically reduce confusion and increase the chance a user takes the right next action.
How do I know if my empty state is hurting retention?
Look at drop-off rates immediately after sign-up or after users complete a key flow. If users land on an empty dashboard and don't return within 24–48 hours, that's usually a signal the empty state gave them no reason to come back. Session recordings in tools like Hotjar often show users sitting still, then leaving.