Skip to content
Sign in
EffectsContour loaderMenu

Contour loaderPro

Topographic contour lines over slowly evolving simplex noise: a calm alternative to a spinner for large panels, an empty canvas or a workspace that is indexing.

ProA workspace panel while the agent indexes a repository: contour lines drift behind the label and a live file count.

Installation

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

Usage

tsx
import { ContourLoader } from "@/components/ui/chatcn/pro/contour-loader"

<ContourLoader label="Indexing the repository" className="aspect-video">
  <span className="tabular-nums">{indexed} of {total} files</span>
</ContourLoader>

The loader fills its box, so give it a size. Lines are traced with marching squares on a 96 column grid (rows follow the aspect ratio, so cells stay square) and drawn as 1 px strokes in two gray tokens, every fourth line one step darker as an index contour. The third noise axis moves with time, so the terrain evolves in place instead of sliding. Lines fade out around the label so text never sits on a line.

Examples

Variants

Dense and sparse maps, a paused loader with a label, a fine slow grid, stronger lines and right to left.

ProDense and sparse maps, a fine grid, stronger lines 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
levelsnumberIso lines. Default 9
indexEverynumberEvery nth line is darker. 0 turns it off. Default 4
gridnumberMarching squares columns. Default 96
scalenumberFeatures across the long side. Default 2.4
speed, fpsnumberDefault 1 and 24 (capped at 30)
seednumberSeeds the terrain, so two loaders differ. Default 3
stillnumberSeconds into the loop for the reduced motion frame. Default 8
stroke, indexStrokestringDefault gray-600 and gray-800 tokens
clearLabelbooleanFade lines around the label. Default true with a label
delaynumberWait before anything shows. Default 300 ms
paused, defaultPaused, onPausedChangeControlled or uncontrolled pause
pauseControlbooleanShow the pause button. Default true

Keyboard

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

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.
  • The pattern fades in over 300 ms when it appears.