Design the dashboard states nobody screenshots
Real dashboards spend much of their time loading, waiting on slow services, showing nothing, failing or hiding data a viewer isn’t allowed to see. This lab lets you trigger each of those states on purpose and see exactly how a well-built dashboard should respond: on screen, to a screen reader and to keyboard focus.
Data arrives in under a second, and content replaces placeholders of exactly the same size.
Open this state in the lab: LoadedFor the first 300 ms nothing changes. Then skeletons hold the shape of what is coming.
Open this state in the lab: LoadingAt 3 seconds the interface says so, instead of spinning in silence.
Open this state in the lab: Slow responseNothing matched the filter. The dashboard says why and offers a way out.
Open this state in the lab: Empty resultsA temporary failure, explained in plain words, with a request ID and a single Retry.
Open this state in the lab: Error with retryNo access is not an error: no Retry, just who can grant access.
Open this state in the lab: Restricted accessSix scenarios, three languages and two independent builds of one spec, tested in Chromium, Firefox and WebKit on every change.
A dashboard is judged less by how it looks with perfect data than by how it behaves while that data is late, missing, failing or off-limits.
The problem
Dashboard designs and demos almost always show the happy path: data arrives instantly, every filter returns results and every viewer may see everything. In production, the other states are common, and they are where trust is won or lost.
What users actually run into
- A spinner that never changes, with no sign of whether anything is still happening.
- Content that jumps around as each widget finishes loading at a different time.
- A blank chart for a filter that simply matched nothing, with no explanation.
- “Something went wrong” for a permissions problem, with a Retry button that can never help.
- Retry buttons that send the same request twice, and late responses that overwrite newer data.
- Screen reader users who hear nothing when data changes, or hear a queue of stale announcements.
- Keyboard focus that jumps to the top of the page when a button disappears.
How it is usually handled
Most teams reach these states the same way, one shortcut at a time:
The design covers the loaded state
Mock-ups show full data. Loading, empty and error states are left for later, or reduced to a single generic spinner and message.
Each component improvises
Every widget adds its own spinner and error text, so timing, wording and recovery differ across the same page.
Testing relies on slowing the network by hand
Someone throttles the browser’s network once, glances at the result and moves on. The behaviour is never written down, so it drifts.
Accessibility is checked at the end
Announcements and focus behaviour are reviewed in a late audit, when changing them is expensive.
None of these steps is wrong on its own. Together, they mean nobody owns the states users see most often under real conditions.
How this lab helps
The lab treats every state as a first-class, specified behaviour, and makes the invisible parts of that behaviour visible.
A written behaviour specification
Timings (nothing for 300 ms, skeletons until 3 s, an explanation at 3 s, Cancel at 8 s), copy, announcements and focus rules are documented, so they can be reviewed and argued with.
Reproducible scenarios
Every combination of latency, failure, role and filter is a link. A designer, developer and tester can all look at exactly the same state.
An inspector for what screenshots miss
A live timeline shows each request and state change, every message sent to screen readers and every programmatic focus move.
One specification, two implementations
The same behaviour is built in React and in plain TypeScript, and one shared test suite checks both, so the comparison is fair.
Three languages, including right-to-left
Everything works in English, Spanish and Arabic, including mirrored layouts, local number formats and plural rules.
How it compares with tools you may already use
The lab is a reference, not a replacement. These tools are excellent at what they do; the table shows where each one stops.
| Tool | Good for | What it does not give you |
|---|---|---|
| Browser developer tools (network throttling) | Slowing or blocking real requests to see what happens. | A definition of what should happen, repeatable states you can share, or a view of announcements and focus. |
| Mock Service Worker (MSW) | Mocking API responses, delays and errors in the browser and in tests. | Guidance on how the interface should respond to those responses. |
| Storybook | Building and documenting components in isolation, one story per state. | Transitions between states over time, such as a slow request becoming an error and then a retry. |
| Component libraries (skeleton, empty and result components) | Ready-made building blocks for each state. | When to show which one, for how long, and what to announce. |
| Data-fetching libraries (TanStack Query, SWR) | Request status, caching, retries and cancellation in code. | The user-facing design of each status. |
| Dashboard UX Lab | A specified, inspectable, testable reference for the behaviour of each state, in three languages. | Production components or a library to install. Use it alongside the tools above. |
Six scenarios to explore
Each one opens the lab in that state. Every scenario also has a written guide.
- LoadedA normal, fast response with full access.700ms
- LoadingA two-second response: long enough to notice, short enough to wait.2s
- Slow responseTwelve seconds. The interface escalates instead of spinning forever.12s
- Empty resultsThe request succeeds but nothing matches the filter.700ms
- Error with retryThe first request fails with a 503. Retrying recovers.1.2s
- Restricted accessThe visitor has no access to this dashboard.700ms
Six rules the lab follows
No skeleton for fast responses.
Placeholders appear only after 300 ms.
Slow is time, not a state.
The message changes at 3 s; Cancel appears at 8 s.
Empty is an answer, not an error.
It names the filter and offers a way out.
Failures are explained and recoverable.
A request ID, one Retry, no double submission.
Forbidden is not failed.
No Retry for a 403; explain who can grant access.
Announce once, move focus rarely.
Only when the focused control disappears.
Who it is for
Front-end developers
See request handling, cancellation, announcements and focus management working together, and read the code behind them.
Designers and product managers
Use the scenarios and guides as a checklist and a shared vocabulary for specifying the states beyond the happy path.
Accessibility reviewers and testers
Reproduce any state from a link and see exactly what was announced and where focus went.
Built and tested with
Break a dashboard on purpose
Slow it down, make it fail, take away access, and watch what a well-built interface does about it.
Where the project stands
The goal: make a dashboard’s behaviour easier to see, hear and test than any browser developer tool does today.
Developer tools show requests and the DOM. They do not show what a person waits through, what a screen reader says, or where keyboard focus lands. This lab is built in the open to show exactly that, one phase at a time.
Done
Foundations
- A written specification of six dashboard states, with timings
- React and framework-free TypeScript builds of it, pixel-identical
- English, Spanish and right-to-left Arabic
- Automated tests in Chromium, Firefox and WebKit
Done
Side-by-side comparison
- Both builds in one view, driven by one set of controls
- Start-up time and download size, measured in your own browser
Done
Real assistive technology
- A screen reader script computed from the spec, checked against both builds on every commit
- Results table and form, open to anyone testing with NVDA, VoiceOver or TalkBack
- Native-speaker review skipped for now: no reviewer is available yet; suggestions stay open
DoneYou are here
See time, not just state
- A request timeline in the lab you can scrub: what was on screen, what was heard and where focus was at any moment
- Any scenario, or any custom settings, exported as a ready-made Playwright test; CI runs every published one
Next
Bring your own
- Paste your own API response and see how each state handles it
- More components: forms, lists and infinite scroll
Read the accessibility statusThe most useful contribution is feedback on the specification: open an issue on GitHub.