Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/voronoi.jsonUsage
import { Voronoi } from "@/components/dithercn/voronoi";
export function Hero() {
return (
<section className="relative isolate min-h-[480px]">
<Voronoi variant="foam" className="absolute inset-0 -z-10" />
<h1>Your headline</h1>
</section>
);
}Every grid square holds one seed, and every point belongs to the seed nearest to it. Each seed wanders inside its square on its own slow loop, so cells grow as their neighbours move away, and two cells that meet at a corner can pull apart while the other two close in.
The walls are measured as the exact distance to the nearest edge, in dither cells, so a wall is the same width along its whole length and at any box size. There are three variants:
"foam": bright thin walls that thicken where three meet, like the borders in a soap foam. Light gathers just inside each wall, and each cell has a faint glint."glaze": flat cells at four exact dither levels, split by open cracks, with a finer crackle running across them, like a crackle glaze or dried mud."stone": raised tiles with rounded corners. Each tile is bevelled towards its edges and lit from the top left, so the edges facing the light are denser.
Examples
Behind a headline
fade={1} clears an oval in the middle of the box. In "glaze", cells fade as a whole by where their seed sits, so every cell stays one flat level and the ones near the middle drop out first.
Coloured mosaic
colors gives each cell one colour from a list. With "stone", a wider wall for the grout and a slower drift, the tiles read as a mosaic that shifts very slowly.
Props
| Prop | Type | Default |
|---|---|---|
| variant Bright walls around dark cells, flat cells split by cracks, or raised tiles lit from one side. | Type: "foam" | "glaze" | "stone" | Default "foam" |
| scale How many cells fit in the box. 1 puts about five across its shorter side; 2 doubles that. | Type: number | Default 1 |
| wall Width of the walls, cracks or grout between cells, in dither cells. Capped at a fifth of a cell's size, so small cells never close up. | Type: number | Default 1.5 |
| drift How fast the seeds wander, so cells swell, shrink and trade edges. 0 holds them still. | Type: number | Default 1 |
| fade How far the cells fade out in the middle of the box, from 0 to 1, to clear room for text. | Type: number | Default 0 |
| colors One colour per cell, picked from this list. Foam walls keep the last palette colour. Palette names such as "blue", CSS variable names, or any CSS colour. | Type: [DitherColor, ...DitherColor[]] | Default – |
| palette Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. | Type: [DitherColor, DitherColor, ...DitherColor[]] | Default ["--dither-paper", "--dither-ink"] |
| matrix The threshold pattern. See Dither engine. | Type: "bayer2" | "bayer4" | "bayer8" | "blue-noise" | Default "bayer4" |
| cell Size of one dither cell in CSS pixels. | Type: number | Default --dither-cell |
| speed How fast it moves. | Type: number | Default 1 |
| animate false stops the motion: a still frame if it starts off, the current frame if it was moving. | Type: boolean | Default true |
| className Size and position. The background fills this box. | Type: string | Default – |
Notes
- It is decorative and hidden from assistive technology. Put real text on top of it, not inside it.
- With reduced motion turned on, it draws one still frame and stops.
- It pauses when scrolled out of view or when the tab is hidden, and releases its WebGL context when far offscreen. Without WebGL it shows the same kind of cells as an SVG, in the same colours.
- Each cell checks 34 seeds, and
"glaze"twice that for its fine crackle, at one pixel per dither cell and 30 fps. Withcolors, the colour pass checks them again. See Performance.