Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/loader.jsonUsage
import { Loader } from "@/components/dithercn/loader";
export function Saving() {
return <Loader size="sm" color="blue" label="Saving changes" />;
}Each variant is a tone field on a small grid of cells. Every frame, the loader thresholds the field with the Bayer matrix and paints the inked cells into a canvas at one pixel per cell. Shape edges are hard, so the ring and the dots keep clean outlines; only the tone inside them changes, so the motion happens in the dither. Cells are 2 pixels by default, big enough that you see the Bayer steps.
ring: a thick ring with a comet on a faint track. The head is solid and the tail steps down through the dither levels behind it. The tail stretches and shrinks as it goes round, so the ring breathes instead of spinning at one pace.pulse: a dot that beats while three rings ripple out from it and thin out as they grow. Good for "waiting for something to arrive".dots: three discs that build up from sparse dither to solid and dissolve again, one after another. Twice as wide as it is tall.bar: a dense head easing across a faint track, its tail thinning behind it. Three times as wide as it is tall.
Examples
Variants and sizes
sm, md and lg are 24, 40 and 64 pixels tall. Cells default to 1 pixel at sm and 2 pixels above it, so larger loaders show more cells and smoother falloff.
In context
A ring next to a deploy, a bar under an upload, and a pulse in an empty event feed that is waiting for its first event. Give each one a label that says what is loading.
Props
| Prop | Type | Default |
|---|---|---|
| variant Which animation to draw. | Type: "ring" | "dots" | "pulse" | "bar" | Default "ring" |
| size Height of 24, 40 or 64 pixels. | Type: "sm" | "md" | "lg" | Default "md" |
| label What screen readers announce. | Type: string | Default "Loading" |
| color A palette name such as "green", a CSS variable name, or any CSS colour. | Type: DitherColor | Default "ink" |
| bloom A soft glow in the loader's colour. | Type: "off" | "low" | "high" | Default "off" |
| matrix The threshold pattern. | Type: "bayer2" | "bayer4" | "bayer8" | "blue-noise" | Default "bayer4" |
| cell Size of one dither cell in CSS pixels. | Type: number | Default 2 (1 at sm) |
| speed Speed multiplier. | Type: number | Default 1 |
Notes
- The root has
role="status"and a visually hiddenlabel, so screen readers announce it politely when it appears. The canvas is hidden from them. - With reduced motion turned on, each variant draws one still frame that still reads as a loader.
- It pauses when scrolled out of view or when the tab is hidden. A loader is at most about a thousand cells, so a frame is cheap, and it uses a 2D canvas rather than WebGL, so loaders never count against the page's WebGL budget.
- Cells snap to whole device pixels, so a loader stays crisp at any zoom level.