Halftone loaderPro
A field of halftone dots whose size rides a slow wave, drawn by the shared shader runtime in two gray tokens. For empty panels and media slots while something larger loads.
On this page
ProAn empty canvas while the first frame of a video renders: halftone dots ride a slow diagonal swell.
Installation
Usage
import { HalftoneLoader } from "@/components/ui/chatcn/pro/halftone-loader"
<HalftoneLoader label="Rendering the first frame" mode="swell" className="aspect-video" />swell rolls a broad diagonal wave bent by slow noise, pulse breathes out from the center and drift wanders like weather. The ground is transparent by default, so the surface behind shows through. It draws through the shared WebGL2 context, so a page of loaders never runs out of contexts, and falls back to canvas 2D.
Examples
Modes
Swell, pulse and drift, a coarse square screen, a paused loader and right to left.
ProSwell, pulse and drift, a coarse square screen and a paused loader.
Props
| Prop | Type | Notes |
|---|---|---|
label | ReactNode | What is happening, in the middle with the shimmer |
ariaLabel | string | Accessible name when label is not text. Default "Loading" |
children | ReactNode | Extra content under the label |
mode | "swell" | "pulse" | "drift" | Default swell |
cellSize | number | CSS px between dot centers. Default 10 |
angle | number | Screen angle in degrees. Default 45 |
wave | number | Waves across the frame height. Default 6 |
range | [number, number] | Smallest and largest dot as coverage. Default 0.04, 0.42 |
ink, paper | string | Default gray-600 token and transparent |
speed, fps, seed, still | number | Default 1, 30, 2 and 2 s |
delay | number | Wait before anything shows. Default 300 ms |
paused, defaultPaused, onPausedChange | Controlled or uncontrolled pause | |
pauseControl | boolean | Show the pause button. Default true |
maxPixels, renderer | Backing store cap and "auto" | "webgl" | "2d" |
Keyboard
| Key | Action |
|---|---|
Tab | Reaches the pause button |
Enter / Space | Pauses or plays the dots |
Accessibility
- The root is
role=statuswith your label as its accessible name. The pattern isaria-hidden. - Content you pass as
children(a live file count, for example) is not announced on every tick. Announce the end of the wait yourself, where the result appears. - The pause button has a name such as
Pause contour lines, a 32 px hit area and 44 px on touch screens. - Under
forced-colors: activethe pattern hides and only the label shows, in system colors.
Motion
- Nothing shows for the first 300 ms, so a fast load shows no loader at all. Once it is visible it stays at least 400 ms, so it never flashes.
- The loop stops offscreen and in a background tab, and the pause button in the corner stops it for good. Paused time does not count, so play continues where it left off.
- Under reduced motion one still frame holds and the label stops shimmering. Nothing moves.
- Under
prefers-contrast: morethe screen gets coarser, so the dots read as solid marks.