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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | "Loading" | Accessible label (used with role="status"). |
label
string
default: "Loading"
Accessible label (used with role="status").
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "ring-inset" | "ring-clip" | "ring-outset" | "ring-dual" | "pulse" | "dots-roll" | "dots-sweep" | ring-inset | Visual spinner style. |
| size | "xs" | "xs-sm" | "sm" | "md" | "lg" | "xl" | number | "md" (40px) | Loader size; accepts a size token or a raw pixel number. |
| color | "default" | "primary" | "secondary" | "info" | "success" | "warning" | "danger" | "white" | — | Shorthand that sets both trackColor and indicatorColor. |
| trackColor | LoaderColor | color ?? "white" | Color of the loader's background track. |
| indicatorColor | LoaderColor | color ?? "default" | Color of the loader's moving indicator. |
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
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | string | "1s" | Animation duration, any valid CSS time value. |
speed
string
default: "1s"
Animation duration, any valid CSS time value.
Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Class applied to the root element. |
| style | CSSProperties | — | Inline styles for the root element. |
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.