# Spinner

An indicator that something is loading.

Source: [spinner.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/spinner.tsx)

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

export default function SpinnerSizes() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Spinner className="size-3" />
      <Spinner />
      <Spinner className="size-5" />
      <Spinner className="size-6" />
      <Spinner className="size-8" />
    </div>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/spinner
```

## Usage

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

export function LoadingProjects() {
  return (
    <span className="flex items-center gap-2 text-sm text-muted-foreground">
      <Spinner />
      Loading projects…
    </span>
  );
}
```

Use `size-*` and `text-*` classes to resize and recolor it. Pass an `aria-label` to say what's
loading.

## Examples

### Colors

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

export default function SpinnerColors() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Spinner className="text-foreground" />
      <Spinner className="text-brand" />
      <Spinner className="text-success" />
      <Spinner className="text-warning" />
      <Spinner className="text-destructive" />
    </div>
  );
}
```

### In buttons

```tsx
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";

export default function SpinnerInButtons() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button variant="secondary" disabled>
        <Spinner />
        Loading
      </Button>
      <Button variant="outline" size="sm" disabled>
        <Spinner />
        Please wait
      </Button>
      <Button variant="secondary" size="icon" disabled aria-label="Loading">
        <Spinner />
      </Button>
    </div>
  );
}
```

### Loading button

Disable the button with `focusableWhenDisabled` while it works, so focus stays put, and point
`aria-labelledby` at its label so the changing text names it.

```tsx
import * as React from "react";

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

export default function SpinnerLoadingButton() {
  const [saving, setSaving] = React.useState(false);
  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined);
  const labelId = React.useId();

  React.useEffect(() => () => clearTimeout(timer.current), []);

  function save() {
    setSaving(true);
    timer.current = setTimeout(() => {
      setSaving(false);
      toast.add({ title: "Changes saved", type: "success" });
    }, 2000);
  }

  return (
    <Button
      variant="primary"
      disabled={saving}
      focusableWhenDisabled
      aria-labelledby={labelId}
      onClick={save}
    >
      {saving && <Spinner />}
      <span id={labelId}>{saving ? "Saving…" : "Save changes"}</span>
    </Button>
  );
}
```

### In context

```tsx
import { Badge } from "@/components/ui/badge";
import { Spinner } from "@/components/ui/spinner";

export default function SpinnerInContext() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Badge variant="muted">
        <Spinner className="size-3" />
        Syncing
      </Badge>
      <span className="flex items-center gap-2 text-sm text-muted-foreground">
        <Spinner />
        Loading projects…
      </span>
    </div>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `Spinner` | `<svg>` |
