Recommended Free Tools
Render a spinner only while a real operation is pending, give it a text label, and make the affected content region announce that it is busy. The example below uses React state for visibility, CSS for rotation, and reduced-motion styling; it does not invent a progress percentage.
Build a spinner that follows a real loading state
This indeterminate spinner tells users that work is in progress, not how much work remains. Keep its visibility tied to the operation’s actual pending state and remove it when that operation finishes.
import { useState } from "react";
import "./LoadingPanel.css";
export default function LoadingPanel() {
const [loading, setLoading] = useState(false);
const [items, setItems] = useState([]);
async function loadItems() {
setLoading(true);
try {
const response = await fetch("/api/items");
if (!response.ok) throw new Error("Could not load items");
setItems(await response.json());
} finally {
setLoading(false);
}
}
return (
<section
aria-busy={loading}
aria-describedby={loading ? "items-loading" : undefined}
>
<button type="button" onClick={loadItems} disabled={loading}>
{loading ? "Loading…" : "Load items"}
</button>
{loading && (
<p id="items-loading" role="status">
<span className="spinner" aria-hidden="true" />
<span>Loading items…</span>
</p>
)}
{!loading && items.length > 0 && (
<ul>
{items.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>
)}
</section>
);
}
The endpoint is illustrative: replace /api/items and the displayed item fields with your own request and data. The finally block clears the busy state whether the request succeeds or throws. In a complete interface, handle the error separately so users can see how to recover; do not leave the region marked busy after the operation has ended.
Style the indicator and honor reduced motion
The circle is decorative because the adjacent text supplies the status. Hiding the visual mark from assistive technology avoids announcing a meaningless shape.
#1 Best Overall
.spinner {
display: inline-block;
width: 1rem;
height: 1rem;
margin-inline-end: 0.5rem;
border: 2px solid currentColor;
border-inline-end-color: transparent;
border-radius: 50%;
vertical-align: -0.15em;
animation: spinner-rotate 0.8s linear infinite;
}
@keyframes spinner-rotate {
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
border-inline-end-color: currentColor;
}
}
The prefers-reduced-motion media feature detects a user preference to minimize non-essential motion. This example replaces continuous rotation with a static ring. W3C’s C39 technique describes responding to the preference to prevent CSS motion; its techniques are examples, not standalone mandatory conformance requirements. W3C: C39, Using the prefers-reduced-motion query to prevent motion.
Connect the status to the content being loaded
In the example, the status text has the ID items-loading; the section references that ID through aria-describedby while loading. The section also has aria-busy="true" only during the request. Together these attributes identify which region is being updated and communicate its busy state. Remove the description reference and clear busy status when the operation ends. WAI-ARIA Authoring Practices: Meter and progress indicators.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
The text “Loading items…” is essential: the rotating mark alone does not explain what is happening. The example gives the text a role="status" so it is exposed as a status message, while the spinner itself is marked aria-hidden="true". Keep the label specific when the interface has several independent loading areas.
Choose a spinner or a progress indicator honestly
Use an indeterminate spinner when the application cannot calculate completion. If it can calculate progress, show the actual value with a progress indicator and keep the displayed and accessible values synchronized. Never attach an estimated or fabricated percentage to an indeterminate animation.
Rank #3
| Choice | When it fits | Value to expose | Semantics |
|---|---|---|---|
| Indeterminate spinner | The operation is pending, but its completion percentage is not known. | No numeric percentage; omit aria-valuenow for an indeterminate ARIA progressbar. |
A progressbar needs an accessible name. For a status-only message such as the example, use status text rather than claiming a measured value. |
| Measurable progress | The application can determine real progress. | The current, truthful progress value, updated as the work proceeds. | Use native <progress> when its behavior and presentation suit the interface; otherwise implement the appropriate accessible progress semantics. |
MDN advises using native <progress> instead of recreating its behavior with ARIA when the native element fits. An ARIA progressbar also requires an accessible name. For an indeterminate ARIA progressbar, leave out aria-valuenow; do not use that role merely to give a spinner a more technical-sounding label. MDN: ARIA progressbar role.
Quick Recap
Best Value
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
Adapt the example to your interface
- For a button action: place the status near the control or result area it describes, and keep it visible only while that action is pending.
- For a full-page wait: provide a clear text message as well as the visual cue, and ensure the page’s busy state corresponds to the work actually underway.
- For several independent requests: track each operation separately so one finished request does not hide another region’s indicator or clear its busy state prematurely.
- For known progress: replace the indeterminate spinner with a value-bearing progress indicator and expose only values the application can really measure.
- For reduced-motion users: retain a non-moving cue and status text rather than relying on rotation to signal activity.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




