Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Free

Ripple Shader for React

Rings spread from points circling the center and interfere: bright lobes and dark lines in one color form a turning, flower-like knot. For React with Base UI, when a hero, a voice or audio feature, or a calm landing page wants water-like motion in a brand color.

Framework

Ripple Shader sends rings outward from three points that slowly circle the center, plus a faster, tighter source at the center itself. Where the rings meet they add up into bright, rounded lobes or cancel into thin dark lines, so a five-petal knot forms around a glowing dot, turns and reshapes, while wavy bands flow out to the darker edges. It's drawn in one color on a dark ground of the same hue, computed for every pixel by a WGSL fragment shader on WebGPU.

Use it behind a hero, a "listening" state for a voice or audio feature, or any section that should feel calm and alive in your brand color. For the same rings in plain CSS, use the Ripple background; for a water surface, Water Shader; for neon interference cells on black, Ripples in Black Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a radial gradient in the same color 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 { RippleShader } from "@/components/ui/ripple-shader";
<RippleShader className="flex min-h-[32rem] items-center justify-center">
  <h1 className="text-5xl font-semibold text-white">Listening</h1>
</RippleShader>

Ripple Shader is a container: your content sits on top of the ripples, 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 ripples are dark in both color schemes, so put white text over them.

Demos

Theme color

color="var(--primary)" draws the ripples in your theme's primary color, and follows a theme switch. sources={5} adds two more sources to the circle, so the knot becomes a rounder, more even rosette of smaller lobes.

Preview width

In a card

sources={2} and frequency={0.7} give a small box fewer, wider rings, so the pattern stays legible at card size.

Preview width

Performance

About 0.1 ms of GPU time a frame at 1280×720 on an M2 Max laptop, among the cheapest shaders in the collection; slower GPUs take several times longer, which still leaves plenty of room. 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 a radial gradient with faint rings in the same color, 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 ripples 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 rings flow outward smoothly, and the knot turns about once every 20 seconds. At the default color, white text has a contrast of about 11:1 on half the frame and above 7.7:1 on three quarters of it. The middle is brighter: there, white text is about 7:1 on half and drops below 3:1 on the brightest lobes and the center dot, so make a centered headline large and bold, or set intensity below 1 for smaller text.

FAQ

How does the wave interference work?

Each source sends out a sine wave that weakens with distance. Every pixel adds up the waves from all the sources at its distance from each. Where crests meet crests, or troughs meet troughs, the sum is large and the pixel is bright; where a crest meets a trough they cancel, which draws the thin dark lines. As the sources circle, the meeting points move, so the pattern turns and reshapes.

How many sources can I use?

From 1 to 5, set with sources; values outside are clamped, and fractions round to the nearest whole number. One source makes a calm, lopsided set of rings; three, the default, the five-petal knot; five, a round, even rosette of smaller lobes. The center source is always there.

How do I change the color?

color takes any CSS color, so "#22c55e", "oklch(0.7 0.2 150)" or var(--primary) all work, and it follows a theme switch. The background, the lobes and the glow are all shades of it, so the whole shader takes its hue. Lighter colors give brighter lobes; very light ones wash the lobes out to white.

What do frequency and intensity do?

frequency sets how close the rings are: 2 packs them twice as tight, 0.5 spreads them out. intensity scales the waves before they're shaded: above 1 the lobes get brighter and the dark lines sharper, below 1 the pattern flattens toward the dark background, which helps text read.

Will it slow my page down?

No. It's about a dozen sines and cosines per pixel, about 0.1 ms of GPU time a frame at 1280×720 on a laptop, and fine on phones. It stops when it's off screen. More sources add a few operations each; quality lowers the resolution if you ever need to.

What happens in browsers without WebGPU?

The box keeps its CSS gradient, a glowing center fading to dark with faint rings, in the same color, so the page still looks finished. Nothing errors: the root's data-state reads unsupported.

API Reference

RippleShader

data-slot="ripple-shader"

PropType
color?string

The ripples' color, from the dark background to the bright crests. Any CSS color. @default "#3399ff".

Default
"#3399ff"
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.

frequency?number

How close the rings are, from 0.5 (wide) to 2 (tight). @default 1.

Default
1
intensity?number

How bright and contrasted the ripples are, from 0 (flat) to 2. @default 1.

Default
1
interactive?boolean

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

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.

sources?number

How many sources circle the center, from 1 to 5. @default 3.

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