Skip to content

Spinner

An animated indicator for an indeterminate loading state.

Stablev0.4.2added in v0.1.0@garn/ui/spinner
On 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.

terminal
npx garn-ui add spinner
First time? Set up garn in your project
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

SelectorState
[data-slot=spinner]spinning

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

Do
  • Give it an accessible name describing what's loading.
  • Match the size to the surrounding control.
Don't
  • 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
AAA2.3.3Animation from Interactions
  • 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).