Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Noise Contours Shader for React

Glossy enamel contours drawn on the GPU: morphing noise cut into crimson bands, silver plateaus and small violet-blue islands, with thick outlines and white glints. For React with Base UI, when a poster-style hero, a music or a fashion page wants a bold, graphic pattern.

Framework

Noise Contours Shader cuts slowly morphing noise into levels and paints them like enamel or liquid vinyl. Most of the surface is a wide crimson band with a brushed, streaky grain, between silver plateaus shaded up each terrace, and the highest ground breaks through as small violet-blue islands, sometimes with a silver dot inside. Thick, dark outlines run along every edge, and white glints catch the crests that face the top left. The noise's gradients turn in place rather than scroll, so the blobs swell, merge and split without drifting. A WGSL fragment shader on WebGPU draws every pixel each frame, for under half a millisecond of GPU time.

Use it behind a poster-style hero, a music, fashion or event page, or anything that wants loud, graphic texture. For playful topographic rings in ink, cream and sunflower, use Smooth Voronoi Contours Shader; for thin, even contour lines over soft noise, Smooth Noise Contours 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 {
  NoiseContoursShader,
} from "@/components/ui/noise-contours-shader";
<NoiseContoursShader className="flex min-h-[32rem] items-center justify-center">
  <div className="rounded-xl bg-black/60 px-8 py-6">
    <h1 className="text-5xl font-semibold text-white">Ship faster</h1>
  </div>
</NoiseContoursShader>

Noise 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 swings from black outlines to silver within a few pixels, so no text color reads on it bare: put copy on a panel, like the bg-black/60 one above, which keeps white text above 5.7:1 everywhere. Big, heavy display type on it can work as a graphic, but not as something to read.

Demos

Retro

bandColor and islandColor recolor the two painted levels, here orange and teal. bands={8} cuts the noise into more, narrower levels, and the painted ones are still the third and the fifth, so they sit lower: the teal spreads through the middle ground and the orange through the valleys, for a busier, two-color map.

Preview width

In a card

tint={0} takes the color out of both painted levels, leaving liquid chrome: silver terraces, black outlines and white glints. It keeps full quality: the outlines and glints are a few pixels wide and blur at half resolution.

Preview width

Performance

About 0.4 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. Each pixel evaluates three gradient noises, two for the field and one for the grain, and gets the field's slope exactly from the same math, so bands, scale and gloss don't change the cost. Keep quality at 1: the outlines and glints are thin and the shader is cheap enough not to need less. 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 silver, black and the band color around an island, 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 of the noise's gradients takes 11 to 25 seconds to turn once, so the blobs morph slowly and the glints slide rather than blink. The contrast inside the pattern is high, so never set small text on it bare: a bg-black/60 panel keeps white text above 5.7:1, and bg-black/70 above 8.5:1.

FAQ

How are the edges kept smooth without multisampling?

The shader knows the noise's exact slope at every pixel, computed alongside the noise itself. Dividing the distance to the next level by that slope turns it into pixels, so each outline is drawn a fixed number of pixels wide and fades over about one pixel, crisp at any scale and on any screen.

Which levels get color?

The noise is cut into bands levels from low to high. The third one takes bandColor, and the fifth takes islandColor. At the default 6 levels the third spreads across most of the box and only the highest ground reaches the fifth, so it forms small islands; with more levels both sit lower and spread further. The rest are silver. So the band color needs bands of 3 or more, and the islands 5 or more.

Do more bands cost more?

No. The noise is computed once per pixel whatever the count; bands only changes where the levels are cut, so 3 and 12 cost the same.

Can I change the red and the blue?

Yes: bandColor and islandColor take any CSS color, var(--primary) included, and follow a theme switch. tint blends both toward silver, from 1 (full color) to 0 (all chrome), and gloss sets the glints, from 0 (matte) to 2 (wet).

Does it move?

It morphs in place: the gradients that shape the noise turn slowly, so blobs swell, merge and split without anything scrolling. speed scales it, speed={0} or paused holds the current frame, and time draws one fixed moment, in seconds.

Will it slow my page down?

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

Credits

Inspired by a 2D noise 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 flow noise and hash, band layout, outlines, glints, grain and colors. The noise's exact slope follows Inigo Quilez's article on noise derivatives (opens in a new tab) (MIT).

API Reference

NoiseContoursShader

data-slot="noise-contours-shader"

PropType
bandColor?string

The wide coloured band, the third level (needs bands of 3 or more). Any CSS colour. @default "#e0193a".

Default
"#e0193a"
bands?number

How many levels the noise is cut into, from 3 to 12. @default 6.

Default
6
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.

gloss?number

The specular glints along the crests, from 0 (matte) to 2 (wet). @default 1.

Default
1
interactive?boolean

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

islandColor?string

The small islands, the fifth level (needs bands of 5 or more). Any CSS colour. @default "#3b2fd0".

Default
"#3b2fd0"
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.

scale?number

Blob size: higher packs more, smaller blobs, from 0.5 to 2. @default 1.

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.

tint?number

How much colour the two coloured levels take, from 0 (all silver) to 1. @default 1.

Default
1
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