What We Learned Auditing Loading States Across a Dozen Client Dashboards
Over the last year we've done loading state audits as part of maybe a dozen different engagements, usually tacked onto a broader front-end review rather than requested on their own. Nobody asks for a "loading state audit" specifically. But the pattern of what we find is consistent enough now that it's worth writing down.
How These Audits Usually Start
A client brings us in for something else entirely, a performance review, an accessibility pass, a general front-end health check, and loading states come up as a side finding almost every time, not because we go looking for them specifically but because they're one of the first things a systematic review surfaces. Once you're clicking through every major flow in an application looking for issues, loading states are impossible to miss, because you hit one on nearly every screen.
We started keeping informal notes after the third or fourth engagement where the same three issues showed up independently, on completely unrelated codebases, built by different teams, in different frameworks. That kind of consistency across unrelated projects usually means the root cause isn't a specific team's mistake. It's a gap in how loading states get taught and reviewed industry-wide.
The Same Three Problems, Almost Every Time
The first is a progress bar that isn't measuring anything real. Somewhere in the codebase, a developer decided a spinner "felt incomplete" and added a bar that fills based on elapsed time rather than actual completion. It looks more informative than a spinner and is actually less honest, because it's implying a measurement that doesn't exist. Users notice this faster than teams expect, usually within the first few uses, and it quietly erodes trust in every other progress indicator in the product afterward.
The second is a loading state with no timeout. If a request stalls, silently, without ever resolving or erroring, the spinner just keeps spinning. We've found this in production more times than we'd like to admit. The fix is small (a timeout that swaps to a retry state after 8 to 15 seconds) but it's almost never there by default.
The third, and the one that surprises people most, is loading states that are completely invisible to screen reader users. A spinner is a purely visual signal. Without an aria-live region announcing when the load completes, a screen reader user gets total silence during the wait and no explicit confirmation that content actually appeared. MDN's documentation on ARIA live regions is the reference we point every team to for this, and WebAIM has good practical examples of applying it to real interfaces, not just spec text.
Why This Keeps Happening
Loading states get built under time pressure, usually as one of the last things wired up before a feature ships, and they get built once and never revisited unless something breaks visibly. Nobody files a bug report that says "your loading state doesn't announce to screen readers," because the people affected by that gap usually can't tell whether it's a bug or expected behavior. It just quietly fails for a subset of users who don't have a clear channel to report it.
The fix isn't more design time. It's a short checklist applied consistently: does the timing match what the system actually knows, is there a timeout, is the completion announced, does the skeleton shape match the real content closely enough to avoid a layout jump. Running through those four questions on any loading state we touch catches the majority of what we find in audits, and we've started handing this same short checklist to client teams directly so they can run it themselves between our engagements rather than waiting for the next audit to surface the same recurring issues again.
What Surprised Us Most
Going in, we expected the accessibility gap to be the rarest finding, something only teams with an existing accessibility practice would have thought about at all. It turned out to be almost universal in the other direction: across the dozen engagements, only one had any aria-live handling around loading state transitions, and that one had it because a specific accessibility audit had flagged it months earlier as a standalone issue.
That tells us the gap isn't really about team skill or care. It's that loading state accessibility sits at the intersection of two things nobody explicitly owns: it's not quite a design responsibility (designers think about the visual shimmer, not the screen reader announcement) and it's not quite treated as a core engineering responsibility either (engineers wire up the data fetching and consider the visual state "done" once it renders correctly). It falls into the gap between those two ownership boundaries more often than almost any other accessibility issue we find.
The fix we recommend is organizational as much as technical: bake the aria-live pattern into whatever shared component or hook handles loading states in the codebase, so it's automatic for every feature built on top of it rather than something each team has to remember to add individually.
The One That Was Hardest to Explain to Stakeholders
The fake progress bar finding is consistently the hardest one to get buy-in on fixing, because it usually tests well in isolation. Show a stakeholder a mockup with a smooth, steadily filling progress bar next to a plain spinner, and the progress bar reads as more polished, more informative, more "finished" as a piece of design. The problem only shows up over repeated real use, once a user has seen the bar jump or stall inconsistently enough times to stop trusting it, and that kind of trust erosion doesn't show up in a single mockup review.
The argument that eventually works is usually a concrete example from the client's own product: pulling up a session recording where a real user watched a fake progress bar behave inconsistently, then comparing it to how a plain, honest spinner with a status label would have handled the same wait. Seeing the actual user reaction, hesitation, repeated clicking, abandoning the flow, tends to land better than an abstract argument about honesty in interface design. Once a team has watched that footage once, the fake progress bar tends to get fixed without much further debate, and it usually becomes the example that gets pulled up the next time a similar shortcut is proposed elsewhere in the product. We've started keeping a short highlight reel of these moments across engagements, with permission, specifically because they do more to change a team's default habits than another slide of abstract UX guidance ever does.
"None of the loading state problems we find are hard to fix individually. What's hard is that they're spread across dozens of components, built by different people, at different times, with no shared checklist. That's a process gap more than a skill gap." - Dennis Traina, founder of 137Foundry
We put the full breakdown of spinners versus skeleton screens versus real progress bars, and when each one is actually the right call, into a longer guide here: designing loading states that don't feel like lying to users. If you want an outside pair of eyes on your own product's loading patterns, that's a normal part of the front-end work we take on at https://137foundry.com.








