What is Loading spinner?
Loading spinner: A loading spinner is a small rotating indicator that shows work is in progress when the duration is unknown, used inside buttons and for short waits.
A loading spinner is the simplest loading indicator: a rotating ring or arc that says "something is happening" and nothing more. It fits where there is no room or no information for anything else: inside a button after it is pressed, beside a field that is checking a username, in the corner of a panel that is refreshing. It is also the most abused indicator, because a page that is nothing but a spinner for several seconds tells the user nothing about what is loading or how long it will take.
Which indicator
- Spinner for waits of roughly one to a few seconds with unknown progress, attached to the control that caused them.
- [Progress bar](/glossary/progress-bar) when progress can be measured, or the wait is long enough that people want to know how far along it is.
- [Skeleton screen](/glossary/skeleton-screen) for first loads of a layout you already know, such as a dashboard or a feed.
- Nothing for waits under about 300ms. A spinner that flashes for a frame is worse than a short pause. Delay showing it, and once shown keep it visible for a minimum time so it does not flicker.
Accessibility
A spinner is usually a CSS animation on an empty element, so it needs text. Give it role="status" and an accessible name such as "Loading" or, better, "Saving invoice", through aria-label or visually hidden text. A role="status" live region is polite, so screen readers announce it once without interrupting. Mark the area being replaced with aria-busy="true" while loading so assistive technology can skip the half-rendered content, and announce completion or move focus to the result when the spinner disappears.
- Inside a button, keep the label in place and add the spinner next to it, so the width does not jump and the name stays readable. Disable the button, or ignore repeat clicks, while the request runs.
- Under prefers-reduced-motion, slow the rotation or swap in a static indicator. A fast spin is one of the motions that bother people most.
- Animate
transform: rotate()only. It runs on the compositor and costs almost nothing, even with several spinners on screen. - Never leave a spinner spinning after an error. Replace it with the error and a retry.
How to build one in React
MiniDev's Spinner is a bordered ring with role="status" and a label for its accessible name:
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
export function SaveButton({ save }: { save: () => Promise<void> }) {
const [pending, setPending] = React.useState(false)
async function onClick() {
setPending(true)
try {
await save()
} finally {
setPending(false)
}
}
return (
<Button onClick={onClick} disabled={pending} aria-busy={pending}>
{pending ? <Spinner size="sm" label="Saving" /> : null}
Save changes
</Button>
)
}
Three sizes cover inline, button and panel use. For blocking a whole panel while it refreshes, LoadingOverlay dims the area and centers a spinner, and LoadingTable shows skeleton rows instead. Both are in the feedback category.
SpinneruiLoading spinner in sm, default and lg sizes with role status and an accessible Loading label. Drop it into buttons, cards or full page states in a Tailwind app.npx shadcn@latest add @minidev/spinnerLoading OverlayuiA translucent overlay that covers its parent with a centered spinner and label while data loads, inheriting the parent radius and announced with role status.npx shadcn@latest add @minidev/loading-overlay