A scene, not a field
Where Shader field sums soft light into an abstract wash, Shader scene paints a place: a planet’s limb at night, or a tear of light opening across the dark. Both read the background field’s colour roles from the page, so a theme, a preset or a scoped override recolours them with nothing to configure.
It is scenery, not interface. It fills the box you give it, ignores the pointer and stays out of the accessibility tree, so placement, clipping and anything that sits on top belong to you.
Scenes
Two scenes, picked with scene. The horizon sets a fixed sun at the top of a curved limb over hazy land and a moving ocean. The rift opens at its middle and streams light outwards. Two stages and no more: each one is a live GL context.
Settings
Each scene’s settings pass as a partial in params, merged over its defaults, so changing one thing takes one key. SHADER_SCENES lists every key with its label and range, which is all a host needs to draw its own sliders. A new value moves the scene on its next frame and never rebuilds the GL state.
Finishing passes
Five passes run over the scene: a scrim, a vignette, fades to the page at the top and the bottom, and film grain. Each is on or off with its own settings in passes. The fades melt the scene into the page ground, and the scrim is the one to reach for when text sits on top.
Light mode
Both scenes are night scenes: they add light to a dark ground. On a light page each one works its light out against a dark ground of its own and lays it onto the page as a pale tint, so it reads as a soft dawn instead of a dark block. Switch the theme to see the stages above change. The scene never sets data-theme itself, which would hide a preset’s colours.
Tuning by drag
A host with sliders should not render on every move. Pass a ref in liveRef and write the full values to it as the pointer moves: the scene reads it every frame in place of the props, and nothing renders until you commit. The playground’s Shaders view tunes its scenes this way.
Reduced motion, fallback and status
Under prefers-reduced-motion the scene holds a still frame, which still answers its settings. speed at 0 does the same on purpose. Where WebGL2 is not available the box shows the page ground and nothing else, so the worst case is a plain page. It reports which happened, as Shader field does: onStatusChange and data-status move from pending to active or unavailable, and enabled={false} switches it off, so a fallback of your own has something to listen to. The loop also stops while the scene is scrolled out of view.
// From the npm package
import { ShaderScene } from 'rift-ds/components/ShaderScene/ShaderScene';
// Or as source you own, via the shadcn CLI
npx shadcn@latest add https://rift-ds.com/r/shader-scene.json