Name the task before choosing an indicator

A spinner tells a customer that something is moving, but it does not say what request was accepted, whether work is still happening, or when it is safe to continue. Begin with the action: opening a product list, applying a filter, loading an account panel, or moving to the next form step. For each, define the request boundary and the result that counts as complete. A waiting message should refer to that task in ordinary language, such as “Updating products”, not a generic “Please wait” on every page. W3C's Status Messages guidance explains that waiting, progress, success and error information may be status messages when shown without changing context. That makes the words and their programmatic exposure part of the customer journey.

Separate the loading of a new page from a small update within the existing page. A full navigation normally gives the browser its own loading and document change cues. A client-side update can leave the old content on screen while new data is pending; without a clear message, a customer may believe the old list already reflects the new filter. Decide whether the old content stays visible, is dimmed, or is temporarily unavailable, then explain that state. Do not make a product look orderable merely because the old card remains while a fresh availability check is underway. This article concerns the immediate page action and its result; long-running uploads and exports need their own job tracking and recovery design.

Match the display to what is measurable

Use a percentage only when the application has a meaningful measure of the work. A downloaded file with known total bytes can support a transfer percentage, though that figure still describes transfer rather than all subsequent processing. A catalogue request whose server duration is unknown cannot honestly promise that it is 70 percent complete because a timer has run for seven seconds. For an unknown duration, show an indeterminate waiting state with a specific task label. MDN's progressbar guidance says an indeterminate progress bar omits aria-valuenow; it also recommends native progress markup where practical. If the process does expose measured steps, define what the numerator and denominator mean before displaying a number.

Consider a filter that has three visible phases: submitting the choice, receiving matching products, and rendering the cards. Showing “three steps” is only honest if those are real state transitions observed by the application. It is not a reason to make a cosmetic bar march forward at a fixed speed. A practical alternative is a short message when the request begins, followed by either the updated results count or an actionable error when it settles. Keep the message stable enough to read; changing it every fraction of a second creates noise. An estimated duration can be useful only when based on trustworthy operational data and clearly described as an estimate. Do not manufacture an estimate to make the interface feel busy.

Expose status without stealing focus

When a page updates in place, a sighted customer can often see a spinner or changed cards, while a screen-reader user may receive no useful signal. W3C's Status Messages criterion addresses messages that can be identified through roles or properties without moving focus to them. Put a concise waiting and completion message where it can be announced appropriately, rather than shifting focus to a spinner on every request. Avoid using urgent alert behavior for routine loading; that can interrupt the customer while they are choosing another option. The visible message and the announced message should agree. A decorative skeleton card cannot carry the whole explanation because it contains no task state in words.

If several pieces of a region update separately, the customer should not hear each partial change as if it were a complete answer. MDN explains that aria-busy can mark a region as being updated and then be cleared when the changes are complete, helping assistive technology avoid premature announcements. This does not replace a readable task label or a completion state. Use it on the relevant region, not indiscriminately on the whole page. Check that it is reliably reset on success, failure and cancellation; a region stuck busy can conceal subsequent updates. The desired sequence is understandable: request accepted, content updating, then actual result or a clear reason the result was unavailable.

Prevent a waiting state from changing the decision

While a new result is pending, decide what the customer can still do. A filter panel may remain editable so the customer can change their mind, but a second request must not let an older response overwrite the newer choice. A checkout price check may need to pause the final pay action until the verified total returns. Those are application-state choices, not properties of a spinner. Show which choice is currently being applied and avoid presenting stale information as current. If a request fails, remove the loading state and offer a retry that preserves the customer's selection. The message should say whether the previous results are still shown, because otherwise the customer might treat them as the failed request's output.

A worked scenario makes the distinction concrete. A shopper selects “in stock” and “under my budget” in a catalogue. The previous unfiltered cards remain visible for a moment while the request runs. The interface shows “Updating products for your filters” and marks the results area busy. When the response arrives, it replaces the cards, announces the actual result count, and clears busy. If the request times out, it says the filters were not applied, preserves the selected filters, and offers Retry. The team does not invent a percentage or claim that a purchase is available based on the old cards. This is a test scenario, not a measured customer outcome or a claim about a particular retailer.

Test every exit from loading

Test a fast success, a slow success, an empty result, a network failure and a cancellation. For each case, note the visible label, the screen-reader announcement, the focus position, the controls still available and the final data shown. A fast result should not flash a distracting indicator so briefly that it becomes meaningless; a slow result needs enough context that the customer knows the action did not vanish. An empty result is a completed request with no matches, not an endless loading state. A failure must end the waiting indicator and describe a next action. If the customer changes filters twice, ensure only the latest completed request controls the final display.

Repeat the tests on a narrow mobile screen, with keyboard navigation and with a screen reader. Check whether the waiting text remains visible when the customer scrolls, whether a control appears disabled without explanation, and whether an announcement occurs when content changes but focus stays put. A progress bar should have a meaningful accessible name, and any numeric value must correspond to measured work. Where a native progress element fits, test it before adding custom ARIA. Review English and Traditional Chinese messages independently: “loading”, “updating” and “complete” are different states, and vague translations can collapse them into one confusing label. The smallest reliable set of states is often easier to maintain than an elaborate animation.

Ship a truthful state model

Write the acceptance record as a state map: idle, request accepted, waiting, success with result, empty result, failure and cancellation where supported. For each transition, define what the customer sees, what assistive technology can determine, and which data is authoritative. Tie the final message to actual completion rather than an animation timer. A team should be able to reproduce the slow and failed paths in a test environment and see the same honest behavior in both languages. If the system cannot measure progress, an accurate task-specific waiting message is a complete design choice. The customer needs enough information to decide whether to wait, retry, change the request or proceed with the returned result.

Plan for the case where the customer leaves before a request finishes. If they switch tabs and return, the interface should reflect the actual latest state, not restart a spinner that implies work is happening again. If they navigate away and back, the page should either restore a valid completed result or make a fresh request with an honest new waiting message. A cancelled request should not later announce success for content that was never displayed. The application may need request identifiers or a latest-response check to keep old responses from winning a race, but the editorial requirement is simpler: the text, controls and displayed result must all refer to the same customer choice.

Give support staff a way to reproduce a complaint without treating a loading graphic as evidence. A customer might report that the filter kept spinning, but the underlying request may have failed, completed without rendering, or been superseded by a later choice. Internal diagnostics should distinguish those paths while keeping customer data out of unnecessary logs. In the public interface, a retry should not silently submit a different choice from the one still shown in the controls. Review the task label, chosen parameters and final outcome together. This also prevents an optimistic “Done” message from appearing before the new content is actually usable. The outcome is a dependable decision surface, not merely an animation that stops.

Before you proceed

  • Can a customer finish the task with keyboard, assistive technology and the visible controls?
  • Does every displayed state match the actual application state?

Sources & further reading