GitHub

Spinner

Animated circular loading indicator. Displays loading state with accessibility support and multiple size options.

AccessibleDark Mode3 Sizes2 VariantsAnimated

Install

$npx react-principles add spinner
01

Live Demo

Explore all variants and interactive states in Storybook.

Open Storybookopen_in_new

Sizes

Loading...
Small
Loading...
Medium
Loading...
Large

Variants

Loading...
Default (primary color)
Loading...
Muted (subtle)

With Text

Loading...
Loading data...
Loading...
Saving changes...
Loading...
Processing file...

On Colored Backgrounds

Loading...
Primary background
Loading...
Dark background
02

Code Snippet

src/ui/Spinner.tsx
import { Spinner } from "@/ui/Spinner";

// Basic usage
<Spinner />

// Sizes
<Spinner size="sm" />
<Spinner size="md" />
<Spinner size="lg" />

// Variants
<Spinner variant="default" />
<Spinner variant="muted" />

// With custom label
<Spinner label="Loading data..." />
03

Copy-Paste (Single File)

Spinner.tsx
import { cn } from "@/lib/utils";

// ─── Types ────────────────────────────────────────────────────────────────────

export type SpinnerSize = "sm" | "md" | "lg";
export type SpinnerVariant = "default" | "muted";

export interface SpinnerProps {
  size?: SpinnerSize;
  variant?: SpinnerVariant;
  label?: string;
}

// ─── Constants ────────────────────────────────────────────────────────────────

const SIZE_CLASSES: Record<SpinnerSize, string> = {
  sm: "w-4 h-4",
  md: "w-5 h-5",
  lg: "w-6 h-6",
};

const VARIANT_CLASSES: Record<SpinnerVariant, string> = {
  default: "text-primary",
  muted: "text-slate-400 dark:text-slate-600",
};

// ─── Component ────────────────────────────────────────────────────────────────

export function Spinner({ size = "md", variant = "default", label }: SpinnerProps) {
  return (
    <div role="status" aria-live="polite" className="inline-flex">
      <svg
        aria-hidden="true"
        className={cn("animate-spin", SIZE_CLASSES[size], VARIANT_CLASSES[variant])}
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
      >
        <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
        <path
          className="opacity-75"
          fill="currentColor"
          d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
        />
      </svg>
      <span className="sr-only">{label || "Loading..."}</span>
    </div>
  );
}
04

Props

PropTypeDefaultDescription
size"sm" | "md" | "lg""md"Size of the spinner. sm is 16px, md is 20px, lg is 24px.
variant"default" | "muted""default"Visual style. default uses primary color, muted is subtle for colored backgrounds.
labelstring"Loading..."Accessible label for screen readers. Override to provide context-specific message.
React Principles