Skip to content

Loader component

Loader is a small, self-contained spinner for indeterminate progress — a request in flight, a section still fetching data, a button mid-submit. It ships with 7 distinct built-in variants rather than one generic spinner, so you can pick whichever visual weight fits the surface it's sitting on.

Feedback9 propsAccessibility notes

Playground

Change a prop on the right and the preview updates. Switch to Code to copy what you see.

Recipes

Copy-paste patterns for the ways Loader actually gets used — each one a real, self-contained example.

Spinner variants

Seven built-in animations — ring styles, a pulse, and dot sequences. Pick one that matches the surrounding UI's weight.

<Loader variant="ring-inset" />
<Loader variant="ring-dual" />
<Loader variant="pulse" />
<Loader variant="dots-roll" />
<Loader variant="dots-sweep" />

Sizes and color

size takes the token scale or a raw pixel number; color tints it to a semantic tone.

<Loader size="sm" />
<Loader size="md" color="primary" />
<Loader size={40} color="success" />

Inside a button

Drop a small white loader into a button for an inline pending state (Button's own isLoading does this for you too).

<Button color="primary" disabled>
  <Loader size="sm" color="white" />
  Saving…
</Button>

Picking a variant

All 7 variants (ring-inset, ring-clip, ring-outset, ring-dual, pulse, dots-roll, dots-sweep) are real, distinct animations, not palette swaps of the same shape — ring-inset (the default) reads as a standard spinner; the dots variants read lighter and work well inline next to text; pulse is the least visually busy option for a subtle "something's happening" indicator.

<Loader variant="ring-inset" />
<Loader variant="dots-roll" size="sm" />
<Loader variant="pulse" color="primary" />

Color: one prop or two

color is a shorthand that sets both the track and the moving indicator to the same color in one step — the common case. For a variant where the track and indicator should differ (a light track with a colored indicator, for instance), set trackColor and indicatorColor independently instead of using color.

<Loader color="primary" />

// Independent track/indicator colors
<Loader trackColor="default" indicatorColor="success" />

Sizing and speed

size accepts the usual xs–xl token scale, or a raw pixel number when you need an exact footprint that doesn't map to a token (say, matching an icon's exact size next to it). speed takes any valid CSS time value if the default 1s animation cycle feels off for the context — slower for a background sync, faster for something the user is actively waiting on.

<Loader size="sm" />
<Loader size={18} />
<Loader speed="0.6s" />

Inside a Button

For a loading button, you almost never render Loader directly — pass isLoading to Button and it renders its own internal spinner (with spinnerPlacement/spinner to customize it) already wired to aria-busy. Reach for a standalone Loader when the loading state belongs to a section of the page, not a specific button.

// A loading section, not a button -- standalone Loader
<div className="flex items-center justify-center p-10">
  <Loader label="Loading dashboard" />
</div>

Props

The full public API for Loader. Every prop is stable and intentionally typed.

Content

label

string

default: "Loading"

Accessible label (used with role="status").

Appearance

variant

"ring-inset" | "ring-clip" | "ring-outset" | "ring-dual" | "pulse" | "dots-roll" | "dots-sweep"

default: ring-inset

Visual spinner style.

size

"xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | number

default: "md" (40px)

Loader size; accepts a size token or a raw pixel number.

color

"default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger" | "white"

default: —

Shorthand that sets both trackColor and indicatorColor.

trackColor

LoaderColor

default: color ?? "white"

Color of the loader's background track.

indicatorColor

LoaderColor

default: color ?? "default"

Color of the loader's moving indicator.

Motion

speed

string

default: "1s"

Animation duration, any valid CSS time value.

Styling

className

string

default: —

Class applied to the root element.

style

CSSProperties

default: —

Inline styles for the root element.

Accessibility

  • Renders with role="status" so assistive technology treats it as a live region announcing an in-progress state.
  • label (defaults to "Loading") supplies the accessible name announced alongside role="status" — override it with something more specific ("Loading dashboard", "Saving changes") whenever there's more than one loading region on a page, so a screen reader user can tell which one just started or finished.

Best practices

  • Always keep label meaningful when a page has more than one Loader at once — the default "Loading" text is fine for a single, obvious spinner, but ambiguous if several regions can be loading simultaneously.
  • For a button's own loading state, use Button's isLoading prop instead of manually rendering a Loader inside it.
  • Match variant weight to context: dots-roll/dots-sweep read lighter for inline or small-scale use; ring-inset/ring-outset read as a more standard, higher-visibility spinner for full-section loading states.
  • Don't rely on Loader alone to communicate what's loading — pair it with surrounding text or a label prop, since a lone spinning icon carries no information by itself.