Spinner
An animated indicator for an indeterminate loading state.
@garn/ui/spinnerOn this page
Default
The default spinner.
Sizing
Glyph size 14/16/20/24/32px — identity (mirrors the button icon ramp).
Labeled
Override the default aria-label.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add spinner- Registry
spinner- Deps
class-variance-authoritylucide-react- Registry deps
utils
Import
import { Spinner } from "@garn/ui/spinner";Props
The component's public props and their types.
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xl" | "xs"- Default
md
Plus 489 inherited native <svg> attributes.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="spinner"]States
| Selector | State | Description |
|---|---|---|
[data-slot=spinner] | spinning | Always animates; paused under prefers-reduced-motion. |
When to use
Indicate an indeterminate loading or pending state, inline or inside a control.
Reach for something else when
- For determinate progress (use a progress bar) or content placeholders (use a skeleton).
Overview
Spinner indicates an indeterminate loading or pending state — inline, beside a label, or inside a control — when you can't report a percentage. Its size follows the same glyph ramp as button icons so it sits cleanly within text and buttons. Use it for short, unknown-duration waits; for a known fraction use Progress, and to reserve a known content shape use a Skeleton.
Guidelines
Use a spinner when duration is unknown and brief. It says "working" without a number; if you can show real progress, a determinate bar is more reassuring, and for laying out incoming content a Skeleton prevents the jump.
Show it in context and accompany long waits. Put the spinner where the result will appear (in the button, beside the row) rather than blanking the whole screen; past a couple of seconds, add a word of context so users aren't left guessing.
Announce loading and respect motion. Give the loading region an accessible "loading" state (e.g. aria-busy/label) so it's not a silent spin, and honor reduced-motion. Don't leave a spinner running forever — resolve to content, empty, or an error.
Best practices
- Give it an accessible name describing what's loading.
- Match the size to the surrounding control.
- Don't block the whole UI with a bare spinner where a skeleton would preserve layout.
Content guidelines
- Give it an accessible name that says what's loading (“Loading results”), not just “Loading”.
- When it sits beside visible text, let that text carry the message and keep the label concise.
Troubleshooting
Screen reader announces nothing useful while loading.
Cause. The default aria-label is the generic “Loading”.
Fix. Pass a specific aria-label, or rely on Button's loading (which sets aria-busy) inside a control.
Accessibility
- Role
status
- role=status with a default aria-label of “Loading”.
- Override aria-label to describe the specific action (e.g. “Saving changes”).
- Animation is paused under prefers-reduced-motion (library-wide guard).