Loader
ProA loader for work the app is waiting on, with its words always beside it ("Loading reports"). It turns only while the work is running and has been heard from: past staleAfter without a report it stops and says "Not heard from since 10:42" (announced once, to a screen reader), and when status says the work is over or isn't running it stops and says so in the status's word. It never turns for a state it can't vouch for, and it isn't for an agent typing (that is Message's caret) or for progress that is measured (that is Progress).
The shape is shape, else the brand's --cd-draw-loader, else a ring. The ring is the loader's own; dots, bars, grid, drive, orbit, pixel, equaliser and wave are drawn by the Pro motion-drawings item's CSS over the same nine hidden marks, so a theme changes the shape without the page changing, and without that item every shape is the ring. None of them is a measurement: the equaliser's bars are a fixed, regular pattern, never levels from data.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0 and 4.3.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/loader
Brings in: locale, motion, relative-time, status, tokens, use-announcer, utils
Usage
"use client"
import * as React from "react"
import { Loader } from "@/components/candor/loader"
export default function Example() {
// The loader turns only while the work keeps reporting: press the button to send it a report, and leave it for 15
// seconds to see it stop and say when it was last heard from.
const [heardAt, setHeardAt] = React.useState(() => Date.now())
return (
<div className="flex w-full max-w-md flex-col gap-4">
<div className="flex flex-col gap-2">
<Loader label="Loading reports" heardAt={heardAt} />
<button type="button" className="self-start text-xs text-accent-text underline-offset-4 hover:underline" onClick={() => setHeardAt(Date.now())}>
A report from the work
</button>
</div>
<Loader label="Exporting the audit log" status="succeeded" />
<div className="flex flex-wrap gap-x-6 gap-y-3">
<Loader label="Syncing calendars" shape="dots" />
<Loader label="Building the index" shape="bars" />
<Loader label="Transcribing the call" shape="equaliser" />
<Loader label="Uploading 3 files" shape="wave" />
</div>
</div>
)
}<Loader>
| Prop | Type | Default | Description |
|---|---|---|---|
| labelrequired | string | — | The work, in the app's words and language: "Loading reports", "Indexing 12 files". Always shown. |
| shape | LoaderShape | undefined | — | The shape: "ring", "dots", "bars", "grid", "drive", "orbit", "pixel", "equaliser" or "wave". Left out, the brand's `--cd-draw-loader` chooses, else the ring. Shapes other than the ring need the Pro motion-drawings item's CSS. |
| status | RunStatus | undefined | — | The work's status as the service last reported it. Left out, the loader turns as long as the work is heard from; any status but "running" stops it, with the status's word beside the label. |
| heardAt | Date | number | null | undefined | — | When the work last reported anything (an event, a byte, a heartbeat), as a Date or milliseconds since 1970. Left out or null, the app isn't tracking it and the loader doesn't claim it has gone quiet. A time that can't be read is no evidence the work is alive, so the loader stops. |
| staleAfter | number | undefined | STALE_AFTER | How long the work may be silent before the loader stops and says so, in milliseconds. Defaults to 15 seconds. |
| className | string | undefined | — | Classes for the root, merged with Candor's. |
Strings
The fixed words, in English by default. Change any of them under loader in <LocaleProvider strings={{ … }}>; the table is exported as loaderStrings.
| Key | English |
|---|---|
| stale | Not heard from since {time} |
| noUpdate | Not heard from |
Motion
| Moment | Family | What it does |
|---|---|---|
| turning | live | The shape turns while the work runs and was heard from within `staleAfter`; it stops the moment the work is over, isn't running, or goes quiet, and is still under reduced motion and at the Off level, where the label alone says the work is going on. |
Set with --cd-motion-loader-<moment>-* in your brand file; the families are in the design reference.
Types
- LoaderShape
- The shapes a Loader draws. The ring is the loader's own; the others are drawn by the Pro motion-drawings item.
"ring" | "dots" | "bars" | "grid" | "drive" | "orbit" | "pixel" | "equaliser" | "wave"- LoaderState
- Whether the loader turns: running, not heard from in time (stale), over or not running (stopped), or no clock yet.
"running" | "stale" | "stopped" | "unchecked"
Types from other items
The props above take these. Each is defined by another item; import it from the path shown.
- RunStatus from @/lib/candor/status
- The status vocabulary every Candor agent component shares. Components take plain data in these shapes rather than an SDK's message objects, so they work with your own LLM drivers, the Vercel AI SDK or anything else; adapters map an SDK's parts onto them.
"idle" // not started | "queued" // accepted, waiting for a worker | "running" // an agent is working on it now | "waiting" // stopped to ask a person (approval, question) | "blocked" // can't start: something it depends on failed or is waiting | "succeeded" | "failed" | "cancelled" | "skipped"