Spinner
The wait, written the way a score writes it: periods, a round, a metronome, a fermata, a breath, a rising chord, or the word itself being inked.
Report an issue with SpinnerExample
Loadingword
Saving your draft
Checking the addressdots
Checking the address
Syncing the libraryround
Syncing the library
Waiting for the printermetronome
Waiting for the printer
Holding your placefermata
Holding your place
Connectingbreath
Connecting
Rendering the previewarpeggio
Rendering the preview
Word, reduced motionLoading
Dots, reduced motion
Round, reduced motion
Metronome, reduced motion
Fermata, reduced motion
Breath, reduced motion
Arpeggio, reduced motion
In a busy button
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/spinner.jsonUsage
This example also uses Button.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/ button.jsonimport { Button } from "@/components/ui/button"import { Spinner, type SpinnerProps } from "@/components/ui/spinner"// Each wait with the words it would stand beside. Alone, a spinner says its label to a screen reader;// in a sentence the words say it, so the sentence is the status and the spinner stays silent.const waits: { variant: NonNullable<SpinnerProps["variant"]>; words: string }[] = [{ variant: "word", words: "Saving your draft" },{ variant: "dots", words: "Checking the address" },{ variant: "round", words: "Syncing the library" },{ variant: "metronome", words: "Waiting for the printer" },{ variant: "fermata", words: "Holding your place" },{ variant: "breath", words: "Connecting" },{ variant: "arpeggio", words: "Rendering the preview" },]export default function Example() {return (<div className="grid grid-cols-[repeat(auto-fill,minmax(8.5rem,1fr))] gap-x-6 gap-y-12 self-stretch sm:grid-cols-[repeat(auto-fill,minmax(11rem,1fr))] sm:gap-x-10 sm:gap-y-16">{waits.map(({ variant, words }) => {const cut = words.lastIndexOf(" ") + 1return (<div key={variant} className={variant === "word" ? "col-span-2 grid content-start gap-3" : "grid content-start gap-3"}><Spinner variant={variant} size="l" label={variant === "word" ? undefined : words} /><span className="ot-label">{variant}</span>{variant === "word" ? (<p className="ot-p"><Spinner variant="word" label={words} /></p>) : (<p className="ot-p" role="status">{/* The last word and the spinner never part at a line break. */}{words.slice(0, cut)}<span className="whitespace-nowrap">{words.slice(cut)} <Spinner variant={variant} /></span></p>)}</div>)})}</div>)}Contract
- Underneath: native.
- Anatomy:
<span class="ot-dots" data-variant="…" aria-hidden="true"><span class="ot-dots-art" aria-hidden="true"><i></i>…</span></span>. The default,dots, is what a busy button holds, beside its present-tense word ("Saving"). Given alabel, the root is insteadrole="status"(polite) with the label in a.ot-sr. Optionaldata-size="l"stands it alone at thefstep; otherwise everything is in em and it takes the size of its line. - Shown only while something the person started is running: the one thing that moves by itself, so every loop is slow and eased like breathing. The art stands on the baseline in a box a capital tall, so it reads as one more glyph; strokes stay hairlines at any size.
- The family, each a way a score or a page writes a wait:
- dots (default): three periods on the baseline, each lifting as it brightens, in turn.
- round: three voices on one hairline ring, each entering a beat after the last, as a round's voices do; they gather at the top and the foot and spread between.
- metronome: a stroke rod on a hairline foot, a dot for its weight, swinging a slow beat.
- fermata: the hold. The arc draws in the reading direction (mirrored right to left), the dot lands under it with spiccato, the pause is held, then the arc lifts away the way it was drawn.
- breath: a dot breathes in and opens into a hairline ring, holds, and closes into a dot again.
- arpeggio: four notes of a chord rising (a third, a third, a fourth), struck in turn with spiccato and let ring.
- word: the label itself in the voice, pencilled; the ink runs through it letter by letter and leaves in the same order. It is always read.
- Reduced motion: nothing travels. Each stops on a frame that still reads as unfinished (the periods trail off, the voices stand apart, the sign of the hold, a small ring, the chord rising in strength, the first letters inked) and the whole mark dims and returns, 2800ms each way, so it still reads as busy.
data-force="reduced"pins that in the docs. - Forced colours: the marks keep the forced colour of the words around them (ButtonText in a button); the word pencils in GrayText.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
variant | "dots" | "round" | "metronome" | "fermata" | "breath" | "arpeggio" | "word" | "dots" | dots: three periods lift in turn. round: three voices chase round a ring. metronome: a pendulum swings a slow beat. fermata: the hold is drawn, held and lifted. breath: a dot opens into a ring and closes. arpeggio: four notes rise, struck in turn. word: the label, inked letter by letter. |
size | "m" | "l" | "m" | m takes the size of the text around it; l stands alone at the section-heading size. |
label | string | Leave it out when the words beside it say what's happening ("Saving"), as in a busy button: the spinner stays silent. Given, it's a polite status read once. word writes it, "Loading" by default, and is always read. | |
className | string | Merged onto the root span. Show it only while something the person started is running. |
Craft
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Loading | Breath | The periods lift as they brighten |
| Round | Canon | Three voices chase round the ring, gathering and spreading |
| Metronome | Breath | The rod swings a slow beat |
| Fermata | Held | The arc draws, the dot lands, the hold, the arc lifts |
| Breath | Breath | The dot opens into a ring and closes |
| Arpeggio | Arpeggio | The notes rise, struck in turn, and ring |
| Word | Written | The ink runs through the word and leaves |