Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Smooth Voronoi Contours Shader for React

A psychedelic topographic map drawn on the GPU: blobby contour bands of ink, cream and sunflower, with one level flooded hot pink, that swell, merge and pinch. For React with Base UI, when a music, art or launch page wants a bold, graphic pattern.

Framework

Smooth Voronoi Contours Shader cuts a blended cellular field into contour bands, like a topographic map drawn for a concert poster. The bands cycle ink, cream, ink and sunflower yellow, ringed by crisp ink outlines, so every hill becomes an eye of concentric rings and one level is flooded hot pink in puddles around them. The points under the field circle slowly, so the blobs swell, merge and pinch like slow lava, and nothing scrolls. A WGSL fragment shader on WebGPU draws every pixel each frame, for about a millisecond of GPU time.

Use it behind a music, art or event page, an album or a launch that wants something loud and graphic. For thin, even contour lines over soft noise, use Smooth Noise Contours Shader; for glossy enamel bands in red and silver, Noise Contours Shader; for the hard-walled cells themselves, Worley Noise Shader; for a calm map in plain CSS, the Topography background. It runs only while it's on screen, holds a still frame with reduced motion, and shows CSS rings in the same colors in browsers without WebGPU.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import {
  SmoothVoronoiContoursShader,
} from "@/components/ui/smooth-voronoi-contours-shader";
<SmoothVoronoiContoursShader className="flex min-h-[32rem] items-center justify-center">
  <div className="rounded-xl bg-white px-8 py-6">
    <h1 className="text-5xl font-semibold text-black">Ship faster</h1>
  </div>
</SmoothVoronoiContoursShader>

Smooth Voronoi Contours Shader is a container: your content sits on top of the pattern, in normal flow. It fills its own box, so give it a height or content. For a backdrop behind the whole page, render it empty with className="fixed inset-0 -z-10". The pattern is half ink and half bright fills, so no text color reads on it bare: put copy on a solid label, like the white one above, or on a bg-black/60 panel, which keeps white text above 6:1 everywhere.

Demos

Glossy

glossy shades every band as a rounded terrace, deeper at its foot and paler at its top, with a thin glint along its upper edge, so the map turns into stacked lacquer. bands={8} cuts the field into fewer, broader levels, which suits the terraces.

Preview width

In a card

The four colors are props. Here fillColor is mint, accentColor orange and paperColor a warmer cream, for a card or an album cover. The outlines keep their width in CSS pixels, so they stay crisp at any size: keep full quality for this one.

Preview width

Performance

About 1 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. Each pixel sums its distance to 32 moving points, 16 for each of two sizes of blob, and gets the field's slope from the same sum, so the outlines cost nothing extra. bands and glossy don't change the cost. The outlines are thin lines, so keep quality at 1 where you can; on a large retina screen quality={0.75} saves almost half the work and softens them only slightly. The animation stops when the shader scrolls out of view, and the browser pauses it in a hidden tab. Every shader on a page shares one GPU device and one frame loop, and vgpu (about 50 KB gzipped) loads only when the first one mounts.

Browser support

The shader needs WebGPU: Chrome and Edge, Safari 26 and later, and Firefox on Windows and macOS. Elsewhere, and until the first frame, the box shows concentric CSS rings of ink, cream and yellow with a pink spot, so the layout never jumps. data-state on the root reads unsupported there, if you want to style around it. See Shader Canvas.

Accessibility

The pattern is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame and stops. Nothing flashes: each point takes 7 to 14 seconds to circle, so the bands creep rather than jump. The pattern swings from ink to cream within a few pixels, so never set text on it bare: a solid bg-white label keeps black text at full contrast, a bg-black/60 panel keeps white text above 6:1, and bg-black/70 above 8.8:1.

FAQ

What is smooth Voronoi?

A Voronoi pattern measures each pixel's distance to the nearest of many scattered points, which leaves hard walls between the cells. Smooth Voronoi blends the distances to all the nearby points instead, so the cells melt into each other and the field has no creases. Cut into levels, it gives the rounded, merging blobs you see here.

How do I get more or fewer lines?

bands sets how many contour levels the field is cut into, from 6 to 24. Fewer give broad, poster-like blobs; more give tight rings, like a detailed map. The accent always floods the middle level, so it stays in view at any count.

What does glossy change?

Each band is shaded as a rounded terrace, deeper at its foot and paler at its top, with a thin glint along its upper edge, so the flat poster becomes stacked lacquer. It's shading painted on each level, not a reflection: there's no light or environment behind it.

Why do the outlines keep the same width?

Along a contour, the field changes faster in some places than others, so a line drawn at a fixed slice of the field would be thick where it's flat and thin where it's steep. The shader computes the field's exact slope at each pixel and divides by it, which turns the distance to the nearest level into pixels, so every outline is 2.5 CSS pixels wide on any screen.

Can I change the colors?

Yes, all four: inkColor for the outlines and every other band, paperColor for the light rings, fillColor for the bright rings and the gradient toward the bottom right, and accentColor for the flooded level. They take any CSS color, var(--primary) included, and follow a theme switch. Keep the ink dark and the paper light, or the rings lose their outlines.

Will it slow my page down?

It costs about 1 ms of GPU time a frame at 1280×720 on a laptop, the same for every bands count and with glossy, and the CPU only sets a few numbers each frame. It stops when it's off screen.

Credits

Inspired by a smooth Voronoi contours shader (opens in a new tab) by Shane on Shadertoy. This is a new shader written from the idea, not a port: its own field, with an exact gradient instead of extra samples, its own band cycle, glossy shading and colors.

API Reference

SmoothVoronoiContoursShader

data-slot="smooth-voronoi-contours-shader"

PropType
accentColor?string

The one level flooded with a loud colour, in puddles. Any CSS colour. @default "#ff2d8a".

Default
"#ff2d8a"
bands?number

How many contour levels the field is cut into, from 6 (broad blobs) to 24 (fine rings). @default 12.

Default
12
fallback?string

What shows until the first frame and in browsers without WebGPU: a CSS background value (gradients, sizes, a colour) that looks like the shader. It can use the colour props' --shader-<field> variables.

fillColor?string

Every other ring of fill, and the gradient's far corner. Any CSS colour. @default "#ffd21f".

Default
"#ffd21f"
glossy?boolean

Shades each band as a rounded terrace with a glint along its upper edge. @default false.

Default
false
inkColor?string

The outlines and every other band. Any CSS colour. @default "#0c0a10".

Default
"#0c0a10"
interactive?boolean

Whether u.pointer follows the pointer. @default false.

paperColor?string

The light rings between the bands. Any CSS colour. @default "#fff9ec".

Default
"#fff9ec"
paused?boolean

Holds the current frame. @default false.

quality?number

Render scale, from 0.25 to 1, of the screen's pixels (capped at 2×). @default 1.

speed?number

Animation speed: 1 is normal, 0 holds the frame. @default 1.

time?number

A fixed moment, in seconds: draws it and doesn't animate.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Forwarded to the part.

All 101 shaders