Skip to content

Spinner

A small loading indicator for work of unknown length: inline with text, inside a button, or over a whole area.

Feedbackv0.1.0experimentalWCAG 2.2 AAView spec
Loading, smallLoading, mediumLoading, large
import { Spinner } from "@rdloom/react";

export default function SpinnerSizesExample() {
  return (
    <div className="flex w-full items-center justify-center gap-6">
      <Spinner size="sm" label="Loading, small" />
      <Spinner size="md" label="Loading, medium" />
      <Spinner size="lg" label="Loading, large" />
    </div>
  );
}

Installation

npx rdloom add spinner

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/spinner.json

Works, but without upgrade tracking.

Usage

import { Spinner } from "@rdloom/react";

<div className="flex w-full items-center justify-center gap-6">
  <Spinner size="sm" label="Loading, small" />
  <Spinner size="md" label="Loading, medium" />
  <Spinner size="lg" label="Loading, large" />
</div>

Inline

Checking your connection

import { Spinner } from "@rdloom/react";

export default function SpinnerInlineExample() {
  return (
    <div className="flex w-full justify-center">
      <p className="flex items-center gap-2 text-sm text-[var(--rd-color-text-muted)]">
        <Spinner size="sm" decorative />
        <span>Checking your connection</span>
      </p>
    </div>
  );
}

Button

import { Button, Spinner } from "@rdloom/react";

export default function SpinnerButtonExample() {
  return (
    <div className="flex w-full justify-center">
      <Button variant="secondary" isDisabled>
        <Spinner size="sm" decorative className="text-current" />
        Saving changes
      </Button>
    </div>
  );
}

Overlay

Invoices

INV-1042, INV-1043, INV-1044

INV-1045, INV-1046, INV-1047

Loading invoices
import { Spinner } from "@rdloom/react";

export default function SpinnerOverlayExample() {
  return (
    <div className="flex w-full justify-center">
      <div className="relative w-full max-w-sm rounded-[var(--rd-radius-control)] border border-[var(--rd-color-border-default)] p-4 text-sm">
        <p className="font-medium">Invoices</p>
        <p className="mt-1 text-[var(--rd-color-text-muted)]">INV-1042, INV-1043, INV-1044</p>
        <p className="mt-1 text-[var(--rd-color-text-muted)]">INV-1045, INV-1046, INV-1047</p>
        <div className="absolute inset-0 flex items-center justify-center rounded-[inherit] bg-[var(--rd-color-surface-default)]/80">
          <Spinner size="lg" label="Loading invoices" />
        </div>
      </div>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
size

Diameter: 16, 20 or 32 px.

"sm" | "md" | "lg""md"
label

The accessible name, read by screen readers as visually hidden text. Say what is loading when it is not obvious, e.g. "Loading invoices".

string"Loading"
decorative

Hides the spinner from assistive technology. Use it when the surrounding control already says it is busy, such as a button labelled "Saving".

booleanfalse

Accessibility

Role status, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

Not interactive.

Screen readers announce

  • The label is announced when the spinner appears
  • A decorative spinner is not announced

What your code must do

  • Not focusable and not interactive
  • Has role status with the label as visually hidden text, so the busy state is announced politely
  • With decorative set it is aria-hidden and has no role
  • With reduced motion the rotation stops and the static arc remains, so it still reads as a loading mark
  • Never the only sign of progress for work that takes long: pair it with text

Guidelines

Use it when

  • A short wait of unknown length
  • A button that is saving or sending
  • Covering an area while its content loads

Avoid it when

  • The wait has a known length: use Progress
  • A page or list shape is known: use Skeleton

Don't

  • Several spinners on one screen at once
  • A spinner with no text for waits over a few seconds
  • Leaving the default label when the page has many spinners

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-action-primary
  • --rd-color-text-muted