Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Free

Valley Mist Shader for React

An ink-wash landscape drawn on the GPU: mountain ridges recede in paler layers while mist pools and drifts in the valleys under a low sun. For React with Base UI, when a calm hero, an outdoor or a journaling page wants a quiet scene with clear sky for its headline.

Framework

Valley Mist Shader paints mountains the way an ink-wash scroll does. Six ridges recede in layers, each a little paler than the one in front, from a dark green-grey at the bottom to a blue-grey haze on the horizon. Every ridgeline is darkest at its crest and fades down into mist that pools in the valley beneath it, and the mist drifts slowly sideways, thickening and thinning as it goes. The ridges glide past at their own parallax speeds, and a pale sun sits low behind the farthest ones. A WGSL fragment shader on WebGPU computes every pixel each frame: each ridgeline is a sum of 1D noise, and the mist is two layers of 2D noise moving at different speeds.

Use it behind a calm hero, an outdoor, travel or meditation page, or a journaling app's welcome screen: the sky fills the top half, clear for a headline. For mist with no land, use the Fog background; for a top-down relief map, Terrain; for the same calm from above the clouds, Cloud Sea Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a gradient of the same sky and ridges 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 { ValleyMistShader } from "@/components/ui/valley-mist-shader";
<ValleyMistShader className="flex min-h-[32rem] items-start justify-center pt-24">
  <h1 className="text-5xl font-semibold text-slate-950">Slow down</h1>
</ValleyMistShader>

Valley Mist Shader is a container: your content sits on top of the landscape, 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 default sky is a pale paper white in both color schemes, so put dark text in the top half, such as text-slate-950; keep it above the ridges, as the nearest ones are as dark as the text.

Demos

Night

The five color props repaint the whole scene. skyColor="#0e1626" and nearColor="#0a0f18" darken it to night, farColor="#2a3a55" turns the distant ridges to moonlit blue, mistColor="#5d6f8f" dims the mist so it glows rather than glares, and sunColor="#e8eeff" makes the low sun a full moon. Put white text in the sky.

Preview width

In a card

In a small box, six ridges crowd together. layers={4} keeps the four boldest, and height={0.55} raises the farthest ridgeline so the land fills more of the card.

Preview width

Performance

About 0.6 ms of GPU time a frame at 1280×720 on an M2 Max laptop, so it runs at full quality; slower GPUs take several times longer. The mist is soft, so quality={0.5} looks almost the same at a quarter of the work, though the ridgelines lose a little of their crispness. 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 from the sky down through the ridge colors, which follows the color props, 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 landscape is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame of the ridges and the mist and stops. Nothing flashes: the mist drifts over tens of seconds. In the top half of the default scene, slate-950 text stays above 17:1; across the middle band, where the far ridges rise, it stays above 6:1 on 95% of the pixels but falls to about 3.5:1 on the darkest crests, so keep body text in the sky. White text doesn't read on the default sky; use it only with dark colors, as in the night demo.

FAQ

How are the ridges and the mist drawn?

Each ridge is a line of 1D noise across the box: a sharp-peaked first octave makes the summits, and three softer octaves add the detail. The shader walks the ridges from front to back and stops at the first one that covers the pixel, blending across the ridgeline so it stays smooth. Below each crest, the mist thickens with depth and pools toward the valley floor, and two layers of 2D noise, stretched sideways and drifting at different speeds, make it thicken and thin.

How do I make the mist thicker or clear it?

intensity sets how thick the mist lies: 0 clears the air so every ridge shows solid, 1 is the default ink-wash, and 2 fills the valleys so only the ridgelines and the nearest hills show. mistColor sets its color, and the low sun tints it near the sun.

How do I leave more or less sky?

height is where the farthest ridgeline sits, from 0.2 to 0.7 of the box's height: the nearer ridges scale down with it, and the sky fills the rest. layers sets how many ridges recede, from 3 to 8: fewer reads bolder in a small box, more gives a deeper range.

Can I match it to my brand or make it dark?

Yes: skyColor, sunColor, mistColor, farColor and nearColor take any CSS color, var(--primary) included, and follow a theme switch. The ridges in between are blends of farColor and nearColor, so those two set the whole range. For a dark page, see the night demo above.

Will it slow my page down?

It costs about 0.6 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. On a slow GPU or a large screen, quality={0.5} renders a quarter of the pixels, and the soft mist hides it.

What happens in browsers without WebGPU?

The box keeps its CSS gradient: the sky at the top, then bands from the far ridge color down to the near one, built from the same color props. Nothing errors: the root's data-state reads unsupported.

API Reference

ValleyMistShader

data-slot="valley-mist-shader"

PropType
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.

farColor?string

The farthest ridges, which the nearer ones darken from. Any CSS color. @default "#b9c4c2".

Default
"#b9c4c2"
height?number

Where the farthest ridges sit, from 0.2 to 0.7 of the height: the sky fills the rest. @default 0.45.

Default
0.45
intensity?number

How thick the mist lies in the valleys, from 0 (clear air) to 2 (only the ridgelines show). @default 1.

Default
1
interactive?boolean

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

layers?number

How many ridges recede into the distance, from 3 to 8. @default 6.

Default
6
mistColor?string

The mist in the valleys. Any CSS color. @default "#eef1ee".

Default
"#eef1ee"
nearColor?string

The nearest ridge, at the bottom. Any CSS color. @default "#2f3b3a".

Default
"#2f3b3a"
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.

skyColor?string

The sky above the ridges. Any CSS color. @default "#f4f1ea".

Default
"#f4f1ea"
speed?number

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

sunColor?string

The low sun behind the ridges and its glow on the mist. Any CSS color. @default "#fff4e0".

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