Spinner React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React loading spinner for waits without a known shape: six styles, from a quiet ring to the iOS spokes, in any size, tone or speed.
It turns on its own and never takes focus. Screen readers hear its label once, as a polite status, and nothing else; inside a parent that already announces the wait (a loading button) pass aria-hidden and it stays silent. With a delay it stays invisible for fast loads and fades in only when the wait gets long enough to notice. Under reduced motion it keeps turning at half speed, so the page still reads as busy. Every style animates only transform and opacity on the compositor, so it keeps moving while the page is busy.
Spinner is a free React component in the React feedback components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add spinner, together with UIProvider and Tones.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add spinnerUsage
Import Spinner from @/components/ui/spinner.
import { Spinner } from "@/components/ui/spinner"
export function ListingsLoading() {
return (
<div className="flex flex-col items-center gap-3 py-12 text-muted-foreground">
<Spinner variant="spokes" size="lg" delay={300} label="Loading listings" />
<p className="text-sm">Searching apartments in New York…</p>
</div>
)
}Props
variantdefault"ring""ring" | "dots" | "bars" | "pulse" | "orbit" | "spokes"ring: a quarter arc over a faint track. dots: three dots in a wave. bars: three bars like an equalizer. pulse: a dot with an expanding ring. orbit: two dots circling. spokes: the iOS activity indicator, 8 spokes (12 from 40px) with the light traveling clockwise.
sizedefault"md""sm" | "md" | "lg" | number16 / 24 / 40px, or any px number (12 at least). sm for inline text and buttons, md for cards and panels, lg for a whole page. The ring stroke and the label grow with it.
tonedefault"current""current" | "neutral" | "brand" | "success" | "warning" | "danger" | "info"current follows the text color, so it works inside any colored parent (a solid button, a colored chip). A tone paints the indicator with the design tokens and the visible label with the readable tone text.
colordefault""stringAny CSS color. Replaces the tone; faded parts use the same color at lower opacity. Leave empty to use the tone.
speeddefault1numberPlayback multiplier: 0.5 is half speed, 2 twice as fast. Clamped to 0.25 to 4. Every style keeps its own rhythm (ring 0.75s, spokes 0.8s, dots 1s per loop).
labeldefault"Loading"stringThe accessible name, announced once as a polite status. Shown next to the indicator with showLabel. Pass aria-hidden when a parent already announces the loading (a loading button): the spinner then drops its status role and this label.
showLabeldefaultfalsebooleanShows the label next to (or under) the indicator. Without it the label is only for screen readers.
labelPositiondefault"end""end" | "bottom"end sits beside the indicator for inline use; bottom stacks it, for page and panel loaders.
trackdefaulttruebooleanRing only: the faint full circle under the arc. Turn it off for a lighter look on busy backgrounds.
delaydefault0numberMilliseconds before anything renders, label included, counted from mount. 300 hides the spinner entirely for fast loads, so it never flashes; after the wait it fades in. A spinner that is already showing never hides again when delay changes (remount it with a key to replay the wait).
pauseddefaultfalsebooleanHolds the current frame. Useful while a pull-to-refresh is still being dragged, or for a still in a gallery.
classNamesPartial<Record<"indicator" | "label", string>>Extra classes for the drawing (svg or span) and the label.
classNamestringExtra classes for the root span, merged last. Override the color with [--spinner-color:...].
styleCSSPropertiesInline styles for the root span.
Examples
Six styles
ring, dots, bars, pulse, orbit and spokes at the default md size.
Sizes
sm 16, md 24, lg 40 or any px number. Spokes switch to 12 from 40px.
Tones and custom color
Six tones, a custom violet, and current color inside a brand chip and a loading button.
Inline and in rows
An sm autosave spinner with its label beside a title, and one trailing a list row in the muted text color.
Delay
Both use delay={300}. The 200 ms request finishes before the spinner would show, so it never flashes.
Page loader
lg spokes with the label stacked under it, for a whole screen that is waiting.
Pull to refresh
Drag the list down: the paused spokes wind up with your finger and start spinning when you let go.
Speed
A multiplier on each style's own rhythm: 0.5, 1 and 2.
Files
- components/ui/spinner.tsx
FAQ
Is the React Spinner component free?
Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.
How do I install Spinner?
Run npx wingo-ui@latest add spinner. It also adds the Wingo UI items it builds on (UIProvider and Tones). With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/spinner.json.
See also
Used in
More React feedback components
Articles using this component
- React Skeleton Loader: Real Shapes, No Layout Shift
Component guides9 min read