A user activates Search. The button changes color, cards disappear, a skeleton flashes, and new results arrive. Visually, the sequence may feel obvious. To a screen-reader user, someone at high zoom, or a person who needs more processing time, the same interface can feel like nothing happened and then the page silently rearranged itself.
Loading, empty, success, timeout, and error states are not decoration around the product. They are the conversation that tells users whether the product heard them, what it is doing, and what they can do next.
Start with a state model, not an aria-live attribute
Before choosing ARIA, list the states the component can enter. A search experience may be idle, loading, partially loaded, successful with results, successful with no results, failed, offline, or timed out. Each state needs a visual message, a programmatic message when appropriate, and a focus decision.
- Visual message: what can a sighted user see, including at high zoom and with color removed?
- Programmatic message: what change should assistive technology announce without moving focus?
- Focus decision: should focus stay where it is, move to a new task, or return after a dismissed layer?
Most state changes should not move focus. Search results appearing, an item being saved, or a background upload completing can be announced while the user remains on the control they are using. Move focus only when the next step genuinely requires immediate interaction, such as a blocking error dialog.
Loading: acknowledge the action without narrating the spinner
A loading state should confirm that work began and prevent duplicate actions where necessary. Use plain language tied to the task: "Searching products," "Uploading annual-report.pdf," or "Checking contrast." Avoid generic announcements such as "Please wait" when the user may have several processes running.
For operations lasting less than a moment, an announcement may create more noise than value. For longer work, announce the start once and the result once. Do not repeatedly announce changing spinner text or every percentage point.
<button type="submit" aria-describedby="search-status">
Search
</button>
<p id="search-status" role="status" aria-atomic="true">
Searching products...
</p>The status role has polite live-region behavior. Put the region in the document before the update occurs so assistive technology can observe its content changing. Replace its text with a concise result such as "18 products found" when the operation completes.
Skeleton screens need meaning outside their animation
A skeleton communicates layout visually but usually contains no information for a screen reader. Hide decorative skeleton shapes from the accessibility tree and provide one textual status for the region. Respect reduced-motion preferences and avoid shimmer effects that can distract people with vestibular, attention, or cognitive disabilities.
Do not place disabled-looking fake buttons and headings into the focus order. The loading placeholder should not create a temporary navigation structure that vanishes as the user reaches it.
Empty states must explain which kind of empty
"No data" can mean at least four things: the user has not created anything, filters removed all matches, data is still syncing, or the request failed. Collapsing these into one empty illustration leaves users guessing.
First-use empty state
Explain what belongs here and provide the primary action: "No reports yet. Run your first audit." Keep the action specific and available to keyboard and voice users.
No search results
Repeat the query or active filters in text, announce the result count, and provide a direct way to clear or edit filters. Do not silently substitute unrelated results without saying that the criteria changed.
Permission-based empty state
If content exists but the user cannot view it, say that access is restricted and identify the next step. "Nothing here" is inaccurate and can cause users to repeat work.
Errors should identify, explain, and preserve
WCAG requires detected input errors to be identified and described in text. When a correction is known, Error Suggestion requires the interface to provide it unless doing so would compromise security or the purpose of the content.
A useful error answers three questions: what failed, why if it is safe to say, and what the user can do next. "Something went wrong" answers none of them. "We could not upload annual-report.pdf because it is larger than 20 MB. Choose a smaller file" lets the user recover.
- Keep valid input. Never erase an entire form because one request failed.
- Associate field errors with their controls and include a visible text message, not color alone.
- At submission, provide an error summary that links to each invalid field when the form is long.
- Move focus only when necessary and tell the user where it moved.
- For server errors, offer retry, save, contact, or alternate-channel actions that actually work.
Success messages need the same care
Silent success creates uncertainty. Users may repeat a payment, upload, or save because they did not perceive the update. Announce the completed action with enough specificity to distinguish it from other work: "Billing address saved" or "Three files uploaded."
Do not announce every autosave. A persistent visual "Saved" indicator and announcements only for meaningful transitions or failures usually create a calmer experience.
Prevent live-region noise
Live regions are powerful because they interrupt the current reading stream. That is also why they are easy to misuse.
- Use polite status messages for normal progress and results.
- Reserve assertive alerts for urgent information that requires immediate attention.
- Announce the outcome, not every DOM mutation that produced it.
- Keep one stable region per task instead of mounting many competing regions.
- Debounce rapidly changing results so typing in a search field does not trigger an announcement after every character.
- Test the spoken sequence with real screen readers; DOM inspection cannot reveal timing and interruption problems.
A test script for every asynchronous component
- Activate the control with keyboard and confirm an immediate visible response.
- Listen for one useful loading announcement when the wait is meaningful.
- Confirm focus remains stable while content updates.
- Test results, no results, slow network, offline, timeout, validation error, and server error.
- Verify retry does not duplicate the original action or erase user input.
- Repeat at 400 percent zoom, with reduced motion, and with a screen reader.
Use the accessible forms guide for field-level patterns and the screen-reader testing guide to verify announcement timing.
The bottom line
The moments between click and result are part of the interface. Name each state, decide what users need to see and hear, keep focus predictable, preserve their work, and make recovery explicit. When the system has nothing to show, it still has something important to say.
Sources
W3C WAI: Understanding SC 4.1.3 Status Messages