ComponentsFeedback

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.

Finding sourcesChecking
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.

Creating the Harvest log table…

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.

Default name
Custom name
Attachment chip
Q3-harvest-forecast.pdf
Hidden beside text

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…

Standalone

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.

variant="default"
variant="default" and "ghost"
variant="outline"
size="sm"
size="onboarding"

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.

Muted, text-sm

Signing you in…

Checking access to this source…

Foreground, text-sm

Copying from Google Drive to Marketing team…

Muted, text-xs

Loading earlier activity…

Uploading…
ThinkingThinking
Mention list
Finding people and files…
Refresh over loaded data

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.

Plan step
Read the planting schedule, Running
Tool call
Result statusSaving to Notion…
Table proposal, 20px slot
Creating Harvest log…

Page loading

Route and app loading use a centered spinner. With text it is hidden; alone it carries the name.

Route pending
Opening TulipFarm…
Spinner only

Load states

A list that loads, then shows rows, an empty state, or an error with a way to retry.

Loading integrations…

Usage

Spinner usageTSX
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

Thought orb for AI and agent workTSX
<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

Leading spinner with a pending labelTSX
<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

Swap with a 20px iconTSX
<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.