Spinner
StableThe loader icon turning once every 900 ms, only when motion is allowed. Decorative next to words; give it a label when it stands alone.
It takes its size from a class and its colour from the text, so it fits beside a line of text, inside a panel or in a button (Button’s loading prop uses it). Without a label it is hidden from screen readers; with one it becomes a small status region.
Updated View source
Three places
import { Button, Spinner } from '@rchursin/product/ui'
/** * Next to words that say what is happening, the spinner is decoration. On its own, give it a * label. Inside a button, use the button's loading prop. With reduced motion it is a still arc. */export default function SpinnerBasic() { return ( <dl className="grid w-full max-w-md grid-cols-1 gap-6 text-center sm:grid-cols-3"> <div className="flex flex-col items-center gap-3"> <dt className="font-mono text-[11px] tracking-[0.12em] text-fg-3 uppercase">Beside words</dt> <dd className="flex h-8 items-center gap-2 text-[13px] text-fg-2"> <Spinner className="size-4 text-fg-3" /> Syncing 3 runs… </dd> </div> <div className="flex flex-col items-center gap-3"> <dt className="font-mono text-[11px] tracking-[0.12em] text-fg-3 uppercase">On its own</dt> <dd className="flex h-8 items-center text-fg-2"> <Spinner className="size-6" label="Loading runs" /> </dd> </div> <div className="flex flex-col items-center gap-3"> <dt className="font-mono text-[11px] tracking-[0.12em] text-fg-3 uppercase">In a button</dt> <dd className="flex h-8 items-center"> <Button loading>Saving…</Button> </dd> </div> </dl> )}Usage
import { Spinner } from '@rchursin/product/ui'<Spinner className="size-4 text-fg-3" /> Syncing 3 runs…<Spinner className="size-6" label="Loading runs" />Examples
Loading a panel
Press Refresh: the list keeps its height and is marked aria-busy, and the result is announced when it lands.
import { useRef, useState } from 'react'import { useAnnounce } from '@rchursin/product/lib/announce'import { Button, Spinner } from '@rchursin/product/ui'
const runs = ['Nightly eval · run 1482', 'Docs agent · run 1481', 'Release notes · run 1480']
/** * A panel that reloads in place. The list keeps its height and is marked aria-busy while the * spinner shows, and the result is announced when it lands ("3 runs loaded"). */export default function SpinnerPanel() { const [loading, setLoading] = useState(false) const timer = useRef<number | undefined>(undefined) const announce = useAnnounce()
function refresh() { setLoading(true) window.clearTimeout(timer.current) timer.current = window.setTimeout(() => { setLoading(false)API
Spinner props
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | 'size-4' | Size and colour classes (it uses the text colour). Replaces the default size. |
label | string | – | Makes it a role="status" holding this text for screen readers. Leave it out when visible words already say what is loading. |
CSS
| Class or variable | What it does |
|---|---|
rc-spin | One turn every 900 ms, only when motion is allowed. With reduced motion the loader stays a still arc. Works on any element. |
Accessibility
- Without a label the icon is decorative and hidden from screen readers; the words next to it do the talking.
- With a label it is a role="status" region holding the text. A region that arrives with its text already inside may not be read by every screen reader, so for a load that matters, also announce the result with useAnnounce.
- Button’s loading state uses the same spinner and sets aria-busy, keeping the button focusable.
- It turns only when the visitor allows motion.
Guidelines
Do
- Put words next to it: “Syncing 3 runs…”.
- Keep the space the content will fill, so nothing jumps when it arrives.
- Announce the result (“3 runs loaded”), not the wait.
Don’t
- Don’t show one for waits under about half a second.
- Don’t cover a whole page with a spinner; load the part that changes.
- Don’t use it for a timer or anything with a known length.
In use
Related
- ButtonThe lit-metal button in five finishes and five sizes, with icons, a loading state and shortcut hints.
- Screen-reader announcementsuseAnnounce makes screen readers say a result like “Moved to Done”, politely or, for urgent things, at once. Nothing shows on screen.
- Autosave statusA quiet line under an editor: “Unsaved changes”, “Saving…”, “Saved · 2 min ago”, and what happens next when a save can’t go through.
- Progress squareProgress drawn as the brand’s square, closing clockwise from the top centre, and a row of squares for a set of focus sessions.
Source
The component’s own files, exactly as they run on this site.