affectUI › Components › Feedback

Spinner

A small turning arc for work that runs right now – a faint ring in the text color, any size, inside a button or a status row, announced only when it has a label.

Free and open source (MIT). Install: npx shadcn@latest add https://www.affectui.com/r/spinner.json · Import from @/components/ui/spinner

When to use

  • Something that works right now and ends on its own: a run, a check, a connect, a save
  • In place of a button's icon while its action runs – the button keeps its width
  • As the status icon of a row that is still running, next to rows that show a check

When not to use

  • A whole area waiting for its data – use the Skeleton, so the page keeps its shape
  • An agent thinking or writing – use the Thinking Loader or the agent's own pixel mark
  • Work with a known length – show its progress, like the Progress Tracker

Usage

import { Spinner } from "@/components/ui/spinner";

export function ConnectButton({ busy, onConnect }: { busy: boolean; onConnect: () => void }) {
  return (
    <button type="button" aria-disabled={busy || undefined} onClick={() => !busy && onConnect()}>
      {busy ? <Spinner /> : <PlugIcon />}
      {busy ? "Connecting" : "Connect GitHub"}
    </button>
  );
}

// On its own, without words next to it: give it a label
<Spinner size={12} label="Checking domain" />

Props

NameTypeDefaultDescription
sizenumber14Width and height in px
labelstringAnnounced as a status (role="status" with aria-label). Leave it out when text next to it already says what happens – then it is hidden from screen readers
classNamestringExtra classes – color it with color, it draws in currentColor
...svg propsComponentProps<"svg">style, ref, data-* – everything goes to the <svg>

Accessibility

  • With label: role="status" and aria-label, so screen readers say what is loading
  • Without label: aria-hidden – the text next to it ("Running", "Connecting") carries the meaning, nothing is read twice
  • Drawn in currentColor, so it always has the contrast of the text around it

Responsive

  • A fixed size in px, so it never pushes text around; sits on the text's middle with vertical-align
  • Dark mode needs nothing: it takes the text color

Requirements

  • React 19

Related

  • Skeleton – Gray shapes in the exact size of the content they stand in for – a soft sheen sweeping across all of them in step, a Loading wrapper that tells screen readers and fades in late, so fast loads never flash
  • Button – Primary, secondary, ghost and destructive buttons with icons, sizes and a shimmering loading state
  • Thinking Loader – A small working pill for AI replies – an eight-ray spinner and a shimmering label that rolls through phases and settles on a check
  • Toaster – Calm toasts from anywhere – a stack that fans out on hover, promise toasts that morph from spinner to check, swipe to dismiss
  • Progress Tracker – Onboarding checklist with an animated progress bar, ticks that draw themselves and auto play