FlutedLight

Moving light behind stationary glass flutes, with adjustable refraction and a static fallback.

Installation

bunx --bun shadcn@latest add undefined/r/FlutedLight.json

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

PropTypeDefaultDescription
variant"soft" | "refracted""soft"Lens profile, light pattern and default palette.
speednumber1Clock multiplier, clamped to 0–4. Zero freezes the current phase.
fluteWidthnumber54CSS pixels per flute; clamped to 12–160.
refractionnumber0.8Lens distortion, clamped to 0–1. Edge shading remains at zero.
pausedbooleanfalseFreeze at the current phase.
respectReducedMotionbooleantruePause when the user prefers reduced motion. Set false only after an explicit opt-in.
lightColorstring—Light tint as #rgb or #rrggbb. Leaves shadows unchanged; invalid values are ignored.
colorsreadonly [string, string, string]Preset paletteInk, midtone and highlight as #rgb or #rrggbb; invalid values use defaults.
childrenReactNode—Foreground content above the decorative canvas.
className, styleNative 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.