A few months back we were doing a UI cleanup pass on a customer dashboard that had been running in production for about two years. The dashboard was fine in most respects. The graphs were correct, the filters worked, the data refreshed on the schedule the spec called for. What was not fine was the empty states. Three of them in particular had grown into small bugs disguised as design decisions, and the time we spent fixing them paid back faster than any other UI change we made that quarter.
This is the postmortem on the three empty states, what they were doing wrong, and what we changed.

Photo by Walls.io on Pexels
Empty state one: the data-source disconnected case
The first one was the worst. The dashboard had an integration with a third-party analytics service that pulled data in every fifteen minutes. When the integration credentials expired, which happened more often than anyone wanted to admit, the integration job silently failed and stopped writing rows to the dashboard's data store. The graphs on the page would then look like every metric had been zero since the failure happened.
The dashboard's empty state was "No data for this period," with a date picker to expand the window. A user who came in and saw a flat line at zero for the last three days would scroll back further, see real data from a week ago, and conclude that whatever business activity the dashboard measured had collapsed. They would page someone, and then the engineering team would figure out that the credentials were expired and not that the business had collapsed.
The fix was to detect the integration's last-successful-sync timestamp and surface it on every empty state. If the last sync was more than two integration cycles ago (so, more than thirty minutes for this dashboard), the empty state changed from "No data for this period" to "Data source has not synced in 47 minutes." A button next to it linked directly to the integration settings page.
After the change, the same kind of credential expiration became a self-service fix. The user saw "data source has not synced," clicked through, reconnected the credentials, and the next sync window pulled real data. Support tickets for "the dashboard is wrong" dropped by an order of magnitude.
The pattern shows up in product engineering teams across industries. The Site Reliability Engineering book chapter on monitoring covers the same principle from the ops side: a silent failure is worse than a loud one because it trains users to ignore the signal.
Empty state two: the filter-too-strict case
The second one was subtler. The dashboard had a set of filter controls that let the user narrow data by date range, geography, and customer segment. Most of the filters were independently fine, but in combination they could exclude the entire data set, leaving the dashboard empty for a perfectly innocent reason.
The empty state in that case said "No results found," which is technically true but operationally useless. The user did not know which filter to relax, did not know whether their data was missing or their filter was too strict, and could not tell the difference between a no-data case and a filter-empty case at a glance.
We changed the empty state to detect the filter state at render time. When any filter was active and the result was empty, the empty state showed the active filters as a list of small chips, and each chip had a remove button. The copy changed from "No results found" to "No results match your filters." A "Clear all filters" button sat next to the list.
The change was small. The behavior change for users was large. Previously they would refresh the page, fight the filter UI, eventually figure out which combination was excluding the data, and complain to support about a confusing UI. After the change, the filter set was visible inside the empty state and they could remove the offending chip in one click. The chip pattern itself is documented well in the Material Design 3 reference for chip components, and most modern UI frameworks have a chip primitive that fits this use case.
Empty state three: the first-time-user case
The third one was the most embarrassing in retrospect. The dashboard's home screen, for a brand-new user with no data, showed the same "No data for this period" empty state as the temporary case for an established user. The new user, who had never put any data into the system, saw the same message as someone whose data source had been integrated for a year and was now empty by coincidence.
This is the single most common empty-state mistake in production software. The new-user case and the temporary-empty case have nothing in common except that the data set is empty, and they need completely different UIs.
The fix was to detect the user's account age and the number of total events ever recorded against this account. If the account had never had any data, the empty state changed to a first-run state with a short explanation of what the dashboard shows, a sample chart of dummy data labeled "Example," and a clear "Connect your data source" button. If the account had data in its history but not in the current view, the existing empty state was fine.
This required two pieces of backend state: account age, and a "has-ever-had-data" boolean. Both were one-line queries that we cached in the user session. The frontend change was a single conditional on which empty state to render.
After this change, activation rates on the dashboard improved meaningfully in the next release cycle. The pattern is covered in the longer piece on how to design empty states that earn trust instead of apologizing, which walks through the recurring shapes.
What we learned about empty-state work in general
The three fixes shared a pattern. Each one was a case where the product had been treating distinct user situations as if they were the same screen. The new-user case is not the same as the temporary-empty case. The filter-empty case is not the same as the no-data case. The integration-broken case is not the same as the no-data case. Treating any pair of these as the same UI is a quiet bug, even if no exception is being thrown.
The discipline that catches these bugs is to walk every screen of the product through four states: never-had-data, used-to-have-data-but-currently-empty, filter-narrows-to-empty, and integration-broken. The first three are real user situations that deserve real UI. The fourth is an engineering failure that the UI should make visible, not hide.
The work is small per fix. The cumulative effect on user trust is large. Once the team starts to see empty states as real UI, the next ten fixes are easier than the first three, and the support burden from "the dashboard is wrong" tickets drops every quarter.
For another angle on the same kind of UI audit, the Apple Human Interface Guidelines on empty views cover the discipline in a native-platform context. The principles transfer cleanly to the web.

Photo by Andrew Neel on Pexels
How to do this kind of audit on your own product
The fast version of the audit is this. Pick three of your most-used screens. For each screen, take the product through the four states above. Note where any pair of states shows the same UI. Each of those is a candidate fix.
You will probably find ten candidate fixes in the first hour. Pick the three that the largest number of real users will see. Ship those in the next sprint. Measure the support-ticket volume for "X is wrong" or "X is broken" tickets in the two months before and after.
The numbers usually speak for themselves. The fixes are not glamorous, they do not show up in a feature changelog, but they reduce the rate at which users churn out of the product after seeing a confusing screen, which is one of the highest-leverage things product engineering can do.
For teams that want help running this kind of audit on a mature product, 137Foundry does this work as part of standard engagements. The 137Foundry services page has more on the engagement model.
The short version
Three empty states on the same dashboard turned out to be three separate bugs disguised as design. The integration-broken case looked like no-data. The filter-empty case looked like no-data. The first-time-user case looked like no-data. Once each was given its own UI, the support burden dropped and the activation rate improved.
The audit takes an hour. The fixes take less than a sprint. The benefit shows up immediately in support volume and gradually in user retention.
Top comments (0)