Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Pro

Glass Caustics Shader for React

Late sun through rippled glass, throwing a slowly wandering net of bright caustic lines across a warm cream surface, drawn on the GPU. For React with Base UI, when a light hero for a home, hospitality or ceramics brand wants a calm, sunlit moment.

Framework

Glass Caustics Shader is the light a carafe of water throws on a table in the late afternoon. An unseen rippled glass bends the sun into a net of sharp bright lines that wander slowly across a warm cream surface, with a faint rainbow along the brightest folds, inside a soft patch of shadow in the lower right; the rest of the box stays plain surface, where your text goes. A WGSL fragment shader on WebGPU computes it from the optics: each pixel's brightness is how much the glass's curvature squeezes the light there, so the lines are true caustics, and red, green and blue focus at slightly different depths, which makes the rainbow.

Use it behind a light hero for a home, hospitality, ceramics or wellness brand, or any page that wants sunlight rather than color. For clear drops that bend a ruled page, use Liquid Glass Shader; for color seen through reeded glass, Fluted Glass Shader. The Caustics background is a uniform blue pool net in CSS. It runs only while it's on screen, holds a still frame with reduced motion, and shows a soft glow where the patch sits 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 {
  GlassCausticsShader,
} from "@/components/ui/glass-caustics-shader";
<GlassCausticsShader className="flex min-h-[32rem] items-center px-12">
  <h1 className="max-w-md text-5xl font-semibold text-slate-900">
    Made for slow mornings
  </h1>
</GlassCausticsShader>

Glass Caustics Shader is a container: your content sits on top of the light, 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 surface is light in both color schemes, so use dark text: text-slate-900 keeps at least 9.7:1 on every pixel, and about 11:1 on the plain left side. spot moves the patch away from your copy.

The ripples and the patch keep their size at any aspect: on wide boxes they're sized to the height, on narrow ones to the width.

Demos

In a card

spot={[0.75, 0.75]} sets the patch's center in the lower right of the card, spread={0.6} widens it a little, and scale={0.7} makes finer ripples, so a small surface shows more of the net.

Preview width

Moonlit pool

surfaceColor="#0c1720" and lightColor="#7fd8ff" turn the table into the floor of a pool at night, and spread={1} fills the whole box with the net. The shadow takes a deeper tone of the surface, so one color sets both. On a dark surface, use white text, bold over the brightest lines.

Preview width

Performance

About 0.7 ms of GPU time a frame at 1280×720 on an M2 Max laptop; slower GPUs take several times longer. The lines are sharp, so it renders at full resolution; quality={0.5} costs a quarter as much, with softer lines. 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 CSS gradient: your surface color with a soft glow and shadow where the patch sits, 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 light 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: the lines drift and fold over seconds, and their brightness never jumps. Dark text reads on every pixel of the default surface (see Usage).

FAQ

Is this a real caustic, or a texture?

It's computed from optics, with no texture. The glass is a rippled height field, six slow waves plus two layers of noise, and each pixel's brightness is how much the glass's curvature focuses or spreads the light falling there. Where it focuses hardest, you get the bright lines. Red, green and blue focus at slightly different depths, which puts the rainbow on the brightest folds.

Can I change the colors?

Yes. surfaceColor is the table, and the shadow under the glass is a deeper tone of it; lightColor is the caustics. Both take any CSS color, theme variables included, and follow a theme switch. For a pool at night, see the Moonlit pool demo.

How do I keep the light away from my text?

spot sets the patch's center, as fractions of the width and height from the top left, [0.7, 0.65] by default, and spread its size, from 0 (a small pool of light) to 1 (the whole box). Put the spot on the side opposite your copy.

How do I make the lines softer or sharper?

focus sets how sharp the lines are, from 0.3 (soft) to 2 (razor thin); intensity their brightness, from 0 to 2; scale the ripple size, from 0.5 (fine) to 3 (broad); and dispersion the rainbow, from 0 to 1. speed sets how fast they wander.

Will it slow my page down?

It costs about 0.7 ms of GPU time a frame at 1280×720 on a laptop, and the CPU only sets a few numbers each frame. It stops when it's off screen, and the props don't change the cost. On a slow GPU or a large screen, quality={0.5} renders a quarter of the pixels.

What happens in browsers without WebGPU?

The box keeps its CSS gradient: your surface color with a soft glow and shadow in the lower right. Nothing errors: the root's data-state reads unsupported.

API Reference

GlassCausticsShader

data-slot="glass-caustics-shader"

PropType
dispersion?number

The rainbow fringe along the brightest lines, from 0 (none) to 1. @default 0.5.

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

focus?number

How sharp the lines are, from 0.3 (soft) to 2 (razor thin). @default 1.

Default
1
intensity?number

The caustics' brightness, from 0 (only the shadow) to 2. @default 1.

Default
1
interactive?boolean

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

lightColor?string

The caustics' color. Any CSS color. @default "#fff4d6".

Default
"#fff4d6"
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

The ripple size, from 0.5 (fine) to 3 (broad). @default 1.

Default
1
speed?number

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

spot?[number, number]

The patch's center, as fractions of the width and height from the top left. @default [0.7, 0.65].

Default
[0.7
spread?number

The patch's size, from 0 (a small pool) to 1 (the whole box). @default 0.5.

Default
0.5
surfaceColor?string

The table, the shadow taking a deeper tone of it. Any CSS color. @default "#f3e9d8".

Default
"#f3e9d8"
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