Skip to content
UI

Spinner

An animated icon that shows an operation is in progress when its duration is unknown.

@zenoxai/ui/components/spinner Markdown
function SpinnerDemo() {
  return <Spinner />
}

Usage

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

export function TimelineLoading() {
  return (
    <div className="flex h-40 items-center justify-center">
      <Spinner aria-label="Loading threat timeline" />
    </div>
  )
}

Use a spinner for a short wait with no measurable progress: a button action, a panel that is fetching. Pick the alternative that tells the user more when there is one:

  • The layout of the content is known in advance (a table, a card): use a Skeleton, which avoids a jump when the content arrives.
  • The operation reports how far along it is (an export, an upload): use a Progress bar.

Spinner is a Loader2 icon from lucide-react that spins. It takes every SVG prop. It inherits the text color and is 16px by default; change both with classes.

Examples

Sizes

Size the spinner to the text or the space around it: size-3 next to small text, the default 16px in buttons and inputs, size-6 or size-8 for a panel or a page.

<>
  <Spinner className="size-3" />
  <Spinner />
  <Spinner className="size-6" />
  <Spinner className="size-8 text-primary" />
</>

In a button

Put the spinner in place of the button’s icon with data-icon="inline-start", keep the label, and disable the button until the action ends. Hide the spinner from screen readers with aria-hidden: the label already says what is happening.

<>
  <Button disabled>
    <Spinner data-icon="inline-start" aria-hidden />
    Scanning
  </Button>
  <Button variant="outline" size="sm" disabled>
    <Spinner data-icon="inline-start" aria-hidden />
    Exporting report
  </Button>
</>

With a label

A spinner next to text that names what is loading. The text carries the meaning, so the spinner is hidden from screen readers. Keep the role="status" element mounted and change its text when the wait ends (“Loaded 128 assets”), so screen readers announce the result.

Loading assets for Acme Corp
<div className="flex items-center gap-2 text-sm text-text-secondary">
  <Spinner aria-hidden />
  <span role="status">Loading assets for Acme Corp</span>
</div>

In a panel

Center a larger spinner in the space the content will take, with a fixed height so the layout does not jump. Without visible text, set an aria-label that names what is loading.

<div className="flex h-40 w-full max-w-sm items-center justify-center rounded-lg border border-border-subtle bg-surface-muted">
  <Spinner
    className="size-6 text-text-muted"
    aria-label="Loading threat timeline"
  />
</div>

Accessibility

  • The spinner renders role="status" with aria-label="Loading". Replace the label with what is loading (“Loading threat timeline”) when the spinner stands alone.
  • Inside a button, set aria-hidden. Otherwise “Loading” becomes part of the button’s accessible name (“Loading Scanning”).
  • A status region added to the page together with its content is often not announced. When the end of the wait matters, announce it from a role="status" element that is already on the page, or with a toast.
  • The animation stops for users who ask for reduced motion; the icon stays visible.

API reference

Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.

Spinner

Spinner is a rotating loader for an action in progress, such as a button waiting for the server. For content that is loading, prefer a Skeleton in its shape.

No props of its own. Accepts the props of the element it renders.