Skip to content
Sign in
EffectsHalftone loaderMenu

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.

ProAn empty canvas while the first frame of a video renders: halftone dots ride a slow diagonal swell.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/halftone-loader

Usage

tsx
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

PropTypeNotes
labelReactNodeWhat is happening, in the middle with the shimmer
ariaLabelstringAccessible name when label is not text. Default "Loading"
childrenReactNodeExtra content under the label
mode"swell" | "pulse" | "drift"Default swell
cellSizenumberCSS px between dot centers. Default 10
anglenumberScreen angle in degrees. Default 45
wavenumberWaves across the frame height. Default 6
range[number, number]Smallest and largest dot as coverage. Default 0.04, 0.42
ink, paperstringDefault gray-600 token and transparent
speed, fps, seed, stillnumberDefault 1, 30, 2 and 2 s
delaynumberWait before anything shows. Default 300 ms
paused, defaultPaused, onPausedChangeControlled or uncontrolled pause
pauseControlbooleanShow the pause button. Default true
maxPixels, rendererBacking store cap and "auto" | "webgl" | "2d"

Keyboard

KeyAction
TabReaches the pause button
Enter / SpacePauses or plays the dots

Accessibility

  • The root is role=status with your label as its accessible name. The pattern is aria-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: active the 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: more the screen gets coarser, so the dots read as solid marks.