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.

Open the labRead the scenario guides

Data arrives in under a second, and content replaces placeholders of exactly the same size.

Open this state in the lab: Loaded

Six 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:

  1. 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.

  2. Each component improvises

    Every widget adds its own spinner and error text, so timing, wording and recovery differ across the same page.

  3. 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.

  4. 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.

Common tools for building and testing dashboard states, and what each covers
ToolGood forWhat 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.
StorybookBuilding 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 LabA 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 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

  • Astro
  • React
  • TypeScript
  • HTML
  • CSS
  • Node.js
  • npm
  • Vitest
  • Playwright
  • ESLint
  • GitHub
  • GitHub Actions

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.

  1. 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
    Open the lab
  2. 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
    See them side by side
  3. 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
    Run the test script
  4. 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
    Scrub a request in the lab
  5. Next

    Bring your own

    • Paste your own API response and see how each state handles it
    • More components: forms, lists and infinite scroll