Loader

Thinking affordances — the staggered three-dot loader and the shimmer status line, both SSR-stable and reduced-motion safe.

LoadingThinking…
<Loader /><Shimmer>Thinking…</Shimmer>

Usage

import { Loader, Shimmer } from "@reva/ui";

Two thinking affordances for the pre-token window and streaming states:

  • Loader — three staggered dots (opacity + a small bounce). The dots paint with bg-current and the root sets text-fg-muted, so a single text-colour class on the Loader retints all three dots at once.
  • Shimmer — a gradient sweep over status text ("Thinking…"). Built on Text (defaults as="span", size="sm"; all Text props pass through except colorthe gradient owns the ink, so the prop is omitted from its type). The sweep never drops below readable ink: the baseline is the muted foreground with a default-foreground highlight passing through, and both stops flip with the colour mode.

Both are SSR-stable: the DOM is identical server and client — only animation values branch — so they hydrate cleanly. And both are reduced-motion safe: Loader renders fully-visible static dots, Shimmer falls back to plain muted text via pure CSS (motion-reduce:), following the OS setting.

Examples

Sizes

Loadingsm
Loadingdefault
Loadinglg
{/* size: "sm" | "default" | "lg" */}<Loader size="sm" /><Loader /><Loader size="lg" />

Retinting

A text-colour class retints the dots through bg-current — here the user-bubble pairing (fg/onSolid ink on the solid primary surface).

Loading
<div className="rounded-lg bg-primary-solid px-3 py-2">  <Loader className="text-primary-fg-onSolid" /></div>

Shimmer status lines

Status copy that updates as the run progresses — swap the children, keep the sweep.

Thinking…Searching the client book…Drafting the rebalance note…
<Shimmer>Thinking…</Shimmer><Shimmer>Searching the client book…</Shimmer><Shimmer size="xs">Drafting the rebalance note…</Shimmer>

Dark canvas

Both components resolve their ink from semantic tokens, so they repaint through the dark cascade with no per-component overrides.

LoadingThinking…
<div className="dark rounded-lg border bg-canvas p-6">  <Loader />  <Shimmer>Thinking…</Shimmer></div>

Accessibility

  • Loader is a role="status" live region, and its aria-label prop renders as a visually-hidden text child (default "Loading") rather than an aria-label attribute — live regions announce text content, not accessible names, so the sr-only text is what makes the Loader's appearance announceable (an empty labelled region typically isn't). One source, no double-naming. Announce-on-mount still varies by screen reader; when the announcement is critical, pair the Loader with visible status text (a Shimmer line) or manage an explicit announcement upstream.
  • Shimmer is real text — readable, selectable, and announced like any other text. The gradient is presentation only; under reduced motion it renders as plain muted text.

Props

ComponentPropTypeDefaultDescription
Loadersize"sm" | "default" | "lg""default"Dot size and gap. No children.
Loaderaria-labelstring"Loading"The status text — rendered visually hidden inside the live region (not as an attribute), so screen readers announce it.
LoaderclassNamestringA text-colour class here retints all three dots (bg-current).
Shimmeras / size / weight / tracking / leadingText propsas="span", size="sm"All Text props pass through — except color, which is omitted because the gradient owns the ink.

On this page