Loader
Small loading indicators for when a reply hasn't started yet, each with an optional label.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/loader-orb.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/loader-orb.json
Usage & API
Example
import { LoaderOrb } from "@/components/ai/loader-orb"
<LoaderOrb />Props LoaderOrb
| Prop | Type & description | Required |
|---|---|---|
label | string | Optional |
size | numberOrb diameter in px. | Optional |
className | string | Optional |
Dependencies
- React
- Tailwind CSS
- motion
Implementation notes
Orb exports LoaderOrb. Props are label, size, default 20, and className. The mark is a round body with a conic gradient of blue, indigo, pink, and sand, a soft glow behind it, and a highlight toward the upper left. It scales slightly while it runs, unless reduced motion is on. The preview centres an unlabelled orb at size 56 and puts the words Thinking… in a sibling span, not in label. A second row uses size 18 and label Reading your files, which is the pattern for a line inside a thread.
Example
import { LoaderDots } from "@/components/ai/loader-dots"
<LoaderDots />Props LoaderDots
| Prop | Type & description | Required |
|---|---|---|
label | string | Optional |
size | numberDot diameter in px. | Optional |
className | string | Optional |
Dependencies
- React
- Tailwind CSS
- motion
Implementation notes
Dots exports LoaderDots. Props are label, size, default 6, and className. Three dots are drawn. The gap between them is size times 0.7, and the row is size times 3 tall so the bounce has room. Each dot waits 0.14s longer than the one before it. With motion allowed they move up by about size times 0.9 and back. With reduced motion they only change opacity. The preview shows an unlabelled row at size 10, then a bubble whose label is Writing a reply, next to a small gradient avatar that is not part of the component.
Example
import { LoaderBars } from "@/components/ai/loader-bars"
<LoaderBars />Props LoaderBars
| Prop | Type & description | Required |
|---|---|---|
label | string | Optional |
size | numberHeight of the tallest bar in px. | Optional |
bars | number | Optional |
className | string | Optional |
Dependencies
- React
- Tailwind CSS
- motion
Implementation notes
Bars exports LoaderBars. Props are label, size, default 16, bars, default 4, and className. Bar width is size divided by 6, and at least 2. Heights follow a repeating pattern of seven scale factors, and each bar loops on its own duration so they do not rise together. Reduced motion leaves them still. The preview centres five bars at size 40 with no label, then a row labelled Transcribing audio beside the text 0:42 / 3:10. That timestamp is demo copy next to the loader, not a prop.
Example
import { LoaderSpinner } from "@/components/ai/loader-spinner"
<LoaderSpinner />Props LoaderSpinner
| Prop | Type & description | Required |
|---|---|---|
label | string | Optional |
size | numberDiameter in px. | Optional |
thickness | numberRing thickness in px. | Optional |
tone | "spectral" | "current""spectral" for the gradient arc, "current" to follow the text colour (e.g. inside a button). | Optional |
className | string | Optional |
Dependencies
- React
- Tailwind CSS
- motion
Implementation notes
Spinner exports LoaderSpinner. Props are label, size, default 18, thickness, default 2, tone, default spectral, and className. The ring is a mask so the arc has a hole. spectral uses a conic gradient and zinc text. current drops the zinc colour, paints the arc with currentColor, and leaves a faint track at 20 percent opacity, so a button can say Saving with a spinner that matches the label. The preview uses size 44 and thickness 2.5 alone, then a chip labelled Running tests at size 15, then a disabled button with tone current and size 14.
Choosing a layout
Use one when the reply has not started and a labelled wait is clearer than an empty bubble. The orb suits a centred pause, or a line such as Reading your files. Dots sit naturally inside a message bubble. Bars suit a job with a duration next to them, such as a transcription. The spinner suits a toolbar or a button that is busy. Pass the label you want the person to read. The component does not rotate through phrases.
Take it away once there is something more specific to show. Reasoning and Chain of thought are that next state: notes, or named steps. A link card that is still fetching its title has its own skeleton inside Link preview, with a sweep across the card, and does not need a second spinner. Do not use a loader as the empty screen of a new chat. That screen is Empty state, which has a greeting, a composer, or a project home, not a status role.
How do I put the spinner on a button without a second colour?
Set tone to current, give the spinner a size that fits the button, and put it in a parent whose text colour is the colour you want. The preview does this on a disabled Saving button: size 14, tone current, and className text-zinc-950 so the arc matches the label. thickness still sets the ring width in pixels. The spectral tone ignores the text colour and always uses the gradient. The label prop is separate. On that button the word Saving is the button's own text, and the spinner has no label, so it exposes Loading to assistive tech.
What should the label say while a chat has no tokens yet?
Say the activity, not a fake percentage. The files use Writing a reply, Reading your files, Transcribing audio, and Running tests. If the mark is decorative next to words you already render, omit label and keep the screen-reader Loading. Do not pass the model's reasoning into label. That text belongs on Reasoning, which takes text and streaming, or on Chain of thought, which takes steps. A loader has no open state and no seconds count.