Spinner
Shows that something is working when you cannot show its shape. Use it in busy buttons, short status lines, and in place of a status icon while a step runs. To hold the space of content that is loading, use Skeleton.
Sizes
Three sizes. The spinner takes the current text color, so it matches the text beside it.
size="sm"12px. Inside badges, such as an integration that is still checking.
size="default"16px. Inline with text and inside buttons of every size.
Loading rows…
size="lg"20px. Swaps with 20px status icons, so the slot does not jump.
Naming
A spinner on its own is a status named “Loading”. Give it a better name, or hide it when text beside it already explains the wait.
Saving access change…
Thinking
variant="thinking" shows a dotted thought orb for AI and agent work. Pick the activity that matches what the agent is doing.
workingParticles on tilted orbits. The general default.searchingA scan line sweeps a dotted globe.solvingBands scramble, then click back into place.listeningA waveform rolls through the rings.connectingA constellation wires itself together.weavingThree strands plait around the sphere.composingAn undulating band, for writing a reply.breathingA ring slowly morphing, for quiet thinking.shapingA dotted outline turns circle, triangle, square.size="lg"20px, beside a chat status line.
Searching your Drive…
Writing a reply…
A status named “Thinking” unless you name it.
Buttons
A leading spinner shows a button is working. The button is disabled and aria-busy, and the label often says what is happening.
Status lines
A status line pairs a hidden spinner with a sentence. It works at text-sm and text-xs, in muted or full text color.
Signing you in…
Checking access to this source…
Copying from Google Drive to Marketing team…
Loading earlier activity…
Uploading…Refreshing Calendar…
- Seed order review · 10:00
- Greenhouse walk · 14:30
Status icons
While work runs, the spinner takes the place of a step, tool, or result icon. Pick a state to see each swap.
Page loading
Route and app loading use a centered spinner. With text it is hidden; alone it carries the name.
Load states
A list that loads, then shows rows, an empty state, or an error with a way to retry.
Loading integrations…
Usage
import { Spinner } from "@tulipfarm/sprout/spinner";
<p role="status" className="flex items-center gap-2 text-sm text-sprout-muted">
<Spinner aria-hidden="true" />
Loading rows…
</p>
<Badge variant="secondary">
<Spinner size="sm" aria-hidden="true" />
Finding sources
</Badge>Spinner renders an SVG that takes the current text color.size sets its size:
"sm": 12px, for badges."default": 16px, for text rows and buttons. Buttons of every size keep it at 16px."lg": 20px, to swap with 20px status icons.
className, style, data-*, and other SVG attributes pass through.
Thinking
<p role="status" className="flex items-center gap-2 text-sm">
<Spinner variant="thinking" activity="searching" aria-hidden="true" />
Searching your Drive…
</p>
<Spinner variant="thinking" size="xl" activity="composing" aria-label="Writing a reply" />variant="thinking" draws a dotted orb fromthinking-orbs(MIT) on a canvas. activity picks one of nine animations:working (default), searching,solving, listening, connecting,weaving, composing, breathing, and shaping. spinnerActivityNames lists them.
"lg": 20px, the default, beside text."xl": 64px, at avatar scale.
The two sizes are separately tuned designs, so the orb has no"sm" or "default". Its code loads the first time a thinking spinner renders, so apps that only use the loader never download it. The orb is monochrome: dark dots on light themes and light dots on dark themes, following the dark andlight classes or the system setting. It does not take the text color. With reduced motion it shows a still frame.
Busy buttons
<Button disabled={pending} aria-busy={pending} onClick={begin}>
{pending && <Spinner aria-hidden="true" />}
{pending ? "Opening sign-in…" : "Connect account"}
</Button>
<Button disabled={saving} aria-busy={saving}>
{saving ? <Spinner aria-hidden="true" /> : <ArrowRight aria-hidden="true" />}
{saving ? "Creating workspace…" : "Create workspace"}
</Button>Status icons
<span className="size-5">
{busy ? <Spinner size="lg" aria-hidden="true" /> : <Database size={20} aria-hidden="true" />}
</span>Accessibility
By default the spinner has role="status" and the name “Loading”, or “Thinking” for variant="thinking". Pass aria-label, such as “Opening workspace”, when the spinner stands alone. When text beside it already explains the wait, pass aria-hidden="true": the role and name are then left out, and the sentence’s own status region is read instead.
The spinner turns once a second and stops when reduced motion is requested. Its fading spokes still read as loading when still. It is never focusable and ignores the pointer.
Theme tokens
The spinner has no color of its own. It uses currentColor, so it follows the text, button, or badge it sits in, in light, dark, and brand themes.