Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Free

Ridgeline Shader for React

Stacked ridgelines drawn on the GPU, after the classic pulsar plot: thin lines rising into noisy peaks in the middle, each hiding the ones behind it. For React with Base UI, when a music, data or label page wants an iconic, monochrome backdrop.

Framework

Ridgeline Shader stacks 48 thin lines, like the classic pulsar plot: each one runs flat at the sides and rises into a cluster of noisy peaks in the middle, and the ground under each line hides the lines behind it, so the stack reads as a landscape seen from the front. The peaks come from value noise that slides slowly with time, so they swell, drift and settle. A WGSL fragment shader on WebGPU walks the lines front to back for every pixel and stops at the first one that covers it, drawing crisp lines that keep their width on steep flanks.

Use it behind a music or label hero, a data or science page, or a footer band: it's monochrome, so it suits any brand. For a live heat map of a signal instead, use Spectrogram Shader; for independent wavy channels with no occlusion, in plain CSS, the Brainwaves background. It runs only while it's on screen, holds a still frame with reduced motion, and shows faint CSS stripes on the same ground in browsers without WebGPU.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Usage

import { RidgelineShader } from "@/components/ui/ridgeline-shader";
<RidgelineShader 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>
</RidgelineShader>

Ridgeline Shader is a container: your content sits on top of the lines, 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 ground is near-black in both color schemes, so use white text. It reads at 19:1 on the bare ground, and the top tenth of the box stays clear of the lines at the default intensity; over the lines, put copy on a bg-black/60 panel, like the one above, which keeps white text above 6.5:1.

Demos

Accent

accentColor fades the far lines into a second color, here a pink-red, from about a third of the way back. lines={24} halves the count, so each line is clearer and the peaks read as separate ridges, which suits an analytics or data page.

Preview width

A short, wide box makes a band for a footer or a section break. spread={1} widens the peaks across the band, intensity={0.6} keeps them low enough to fit, and lines={20} keeps the lines apart at this height.

Preview width

Performance

About 1 ms of GPU time a frame at 1280×720 on an M2 Max laptop with the default 48 lines; slower GPUs take several times longer. Each pixel checks only the lines whose peaks can reach it, front to back, and stops at the first one that covers it; at the flat sides that's one or two. More lines or a higher intensity means more lines to check in the middle. Keep quality at 1: the lines are 1.5 CSS pixels wide and blur at half resolution. 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 faint horizontal stripes and a soft glow in the line color on the same ground, 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 lines are decoration: they're hidden from assistive tech and let every click through to your content. With reduced motion it draws one still frame and stops. Nothing flashes: the peaks rise and fall over seconds, and the fine jitter on each line moves slowly. White text reads at 19:1 on the bare ground, but not over the lines: put copy there on a bg-black/60 panel (white text above 6.5:1) or bg-black/70 (above 9:1).

FAQ

What is the pulsar plot?

A chart of radio pulses from the first pulsar discovered, about 80 successive pulses drawn as stacked lines, each hiding the ones behind it. It was printed in an astronomy reference in the 1970s and became famous on an album cover. This shader draws the same kind of stack from noise, not from data.

How do the lines hide each other?

Each line has a solid fill under it in fillColor. The shader walks the lines from the front, at the bottom, to the back, and the first line whose fill or stroke reaches the pixel wins, so a tall peak in front covers the lines behind it, just as in the printed plot.

How do I change the peaks?

intensity sets how tall they rise, from 0 (flat lines) to 2, and spread how far they spread from the middle, from 0.2 (a narrow column) to 1 (most of the width). lines sets how many lines are stacked, from 16 to 80.

Can I change the colors?

Yes: lineColor is the lines, fillColor the ground that hides them, and accentColor, off by default, a color the far lines fade into. They take any CSS color, var(--primary) included, and follow a theme switch. Dark lines on a light fill work too, for a printed look.

How do I make it faster or slower?

speed scales time: 2 makes the peaks shift twice as fast, 0.5 half as fast, and 0 holds the frame. paused holds it too, and time draws one fixed moment, in seconds.

Will it slow my page down?

It costs about 1 ms of GPU time a frame at 1280×720 on a laptop with 48 lines, and the CPU only sets a few numbers each frame. It stops when it's off screen. On a slow GPU, lower lines before quality, which would blur the thin lines.

API Reference

RidgelineShader

data-slot="ridgeline-shader"

PropType
accentColor?string

A color the far lines fade into, such as "#ff4d6d". Any CSS color; unset, every line is lineColor.

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

The ground under each line, which hides the lines behind it. Any CSS color. @default "#0a0a0a".

Default
"#0a0a0a"
intensity?number

How tall the peaks rise, from 0 (flat lines) to 2. @default 1.

Default
1
interactive?boolean

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

lineColor?string

The lines. Any CSS color. @default "#f2f2f2".

Default
"#f2f2f2"
lines?number

How many lines are stacked, from 16 to 80. @default 48.

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

spread?number

How wide the peaks spread from the middle, from 0.2 to 1. @default 0.5.

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