FlutedLight
Moving light behind stationary glass flutes, with adjustable refraction and a static fallback.
Installation
Usage
Install using the command above in a React app with Tailwind and shadcn configured
(components.json and its cn utility). The registry copies three source files
into components/FlutedLight; no animation package, image asset or API key is
needed. Import from your own components directory, not from @vonwerk/ui.
You can also download the registry JSON and install it locally with
bunx --bun shadcn@latest add ./FlutedLight.json.
Hero or card
import { FlutedLight } from "@/components/FlutedLight";
<FlutedLight
variant="refracted"
className="grid min-h-[600px] place-items-center rounded-2xl px-6 py-20"
>
<div className="text-center text-white">
<h1 className="text-5xl">Light in motion.</h1>
<a href="/explore" className="mt-8 inline-block rounded-full bg-white px-6 py-3 text-black">
Explore
</a>
</div>
</FlutedLight>Set the container's height or padding explicitly. Foreground children remain
interactive. The default minimum height is 200px; override it with min-h-0 for
smaller cards. Standard div props such as id, style and aria-label pass through.
Use variant="soft" for the original gentle bands (the default), or
variant="refracted" for warmer, more irregular light with longer trails at the
glass edges. Thin highlights catch that moving light without breaking up the
soft connected band. Both presets share this component, its controls and renderer.
Switching presets retains the animation phase. Custom colors override the
preset palette. Use lightColor to change only the colored light: it sets the
midtone and derives a lighter highlight while keeping the shadow color intact.
If both are supplied, lightColor overrides the last two entries of colors.
<FlutedLight variant="refracted" lightColor="#6baab5" className="h-[600px]" />Shared page background
Place one instance behind transparent sections to keep the light continuous across the page. Keep the fixed background outside transformed ancestors.
import { FlutedLight } from "@/components/FlutedLight";
export default function Page() {
return (
<div className="relative isolate min-h-screen text-white">
<FlutedLight
aria-hidden="true"
variant="refracted"
lightColor="#6baab5"
className="pointer-events-none fixed inset-0 -z-10"
/>
<main>
<section className="grid min-h-screen place-items-center p-8">Hero content</section>
<section className="grid min-h-screen place-items-center p-8">Closing content</section>
</main>
</div>
);
}paused and speed are controlled props, so they can be driven by your own
settings or visibility controls. Each instance owns its renderer and clock.
The component works inside a Server Component; only custom interactive controls
need a client boundary. The demo controls and sample content are not installed.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "soft" | "refracted" | "soft" | Lens profile, light pattern and default palette. |
speed | number | 1 | Clock multiplier, clamped to 0–4. Zero freezes the current phase. |
fluteWidth | number | 54 | CSS pixels per flute; clamped to 12–160. |
refraction | number | 0.8 | Lens distortion, clamped to 0–1. Edge shading remains at zero. |
paused | boolean | false | Freeze at the current phase. |
respectReducedMotion | boolean | true | Pause when the user prefers reduced motion. Set false only after an explicit opt-in. |
lightColor | string | — | Light tint as #rgb or #rrggbb. Leaves shadows unchanged; invalid values are ignored. |
colors | readonly [string, string, string] | Preset palette | Ink, midtone and highlight as #rgb or #rrggbb; invalid values use defaults. |
children | ReactNode | — | Foreground content above the decorative canvas. |
className, style | Native div props | — | Container sizing and styling. Other div props are forwarded. |
Motion and fallback
The renderer uses one WebGL triangle, no textures or third-party animation library. It adapts to container size and caps drawing resolution at 1.5 DPR and about two million pixels. Hidden tabs stop the clock. Pausing, changing props and resizing preserve the current phase. Resources, event handlers and animation frames are released on unmount.
Reduced motion gets one still frame by default. Unsupported WebGL or context loss
reveals an original CSS illustration. Remount the component to retry after context
loss. The docs preview keeps each preset's chosen light color separately. Changing color redraws
a paused frame without resuming motion. The docs preview resumes motion when its Speed control is moved; the reusable
component itself follows its controlled paused and respectReducedMotion props.
Inspired by https://ornn.com/ — steal-ui entry ornn-fluted-gold-background.