Light rays background
Shafts of light falling from above with haze and floating dust, drawn with WebGL. They shimmer without repeating, the source follows the pointer a little.
A stage lit from above
Light rays fall in shafts from above, with haze and floating dust in the beams, drawn with WebGL. They turn a headline into something on a stage and guide the eye straight to it, while a scrim keeps the text readable.
Hero from above lets light fall straight onto a centered headline, like through a skylight. Morning band slants the light in from the upper right across a band of figures. Spotlight card lights the one card that matters most with a narrow beam. Behind a call to action opens a wide, slow fan of light over the closing panel.
Rays work best with a short, centered headline right where the light lands. Use the spotlight card for the plan or feature you recommend, the beam does the pointing.
Variants
Details
What Light rays brings along, read from the registry and the source.
- Category
- 3D backgrounds
- Area
- Visuals
- Variants
- 4
- Version
- 1.0.0 · since 1.2.0
- File
- components/ui/light-rays.tsx
- Brings along
- utilskitwebgl
- npm packages
- None
- Languages
- Texts come through props
- Studio
- Adjust in code
- Build with AI
- Over MCP in Claude, Cursor, Copilot and Codex
Props
The LightRaysProps interface from components/ui/light-rays.tsx, with the comments from the code. Also takes every prop of ShaderLayerProps.
originoptionalLightRaysOrigintoptop-lefttop-right
Where the light comes from
reachoptionalnumberHow far the rays reach down, 0.3 to 1.5, default 1
spreadoptionalnumberOpening of the cone, 1 is the designed width
dustoptionalbooleanFloating dust in the light, default true
Accessibility
Read from the source of the component.
- Needs no client state. The server writes it in full, so it reads without JavaScript too.
Motion
- No animations, it stands still from the start.
Source
The file aiw add puts into your project. From then on it is yours.
Show the file, 111 lines
'use client';
import { GLSL_NOISE, ShaderBackdrop, ShaderCanvas, type ShaderLayerProps } from '@/components/ui/webgl';
/*
* Shafts of light falling from above, like sun through a high window. The
* rays are noise along the angle around a source above the box, layered in
* three widths that drift at different paces, so they shimmer without ever
* repeating. A thin haze and slow floating dust give the light its volume.
* Soft by nature, rendered at reduced resolution.
*/
export type LightRaysOrigin = 'top' | 'top-left' | 'top-right';
export interface LightRaysProps extends ShaderLayerProps {
/** Where the light comes from */
origin?: LightRaysOrigin;
/** How far the rays reach down, 0.3 to 1.5, default 1 */
reach?: number;
/** Opening of the cone, 1 is the designed width */
spread?: number;
/** Floating dust in the light, default true */
dust?: boolean;
}
const ORIGIN: Record<LightRaysOrigin, number> = { 'top-left': -1, top: 0, 'top-right': 1 };
const FRAG = `${GLSL_NOISE}
uniform float u_origin;uniform float u_reach;uniform float u_spread;uniform float u_dust;
void main(){
float m=min(u_res.x,u_res.y);
vec2 p=(gl_FragCoord.xy-.5*u_res)/m;
float hw=.5*u_res.x/m,hh=.5*u_res.y/m;
vec2 src=vec2(u_origin*hw*.8+(u_mouse.x-.5)*.25*u_hover,hh+.35);
vec2 d=p-src;
float dist=length(d);
float ang=atan(d.x,-d.y);
float axis=-u_origin*.42;
float ca=(ang-axis)/(.62*u_spread);float cone=exp(-ca*ca);
float t=u_time;
float r=noise(vec2(ang*9./u_spread,t*.12))*.5+noise(vec2(ang*23./u_spread+3.,-t*.2))*.32+noise(vec2(ang*51./u_spread+7.,t*.35))*.18;
r=pow(smoothstep(.25,.95,r),1.6);
float len=hh*2.*u_reach+.3;
float fall=smoothstep(len,0.,dist-.35)*smoothstep(0.,.5,dist);
float haze=fbm(p*1.6+vec2(0.,t*.05))*.35;
float v=cone*fall*(r*.85+haze*.4+.06);
float dust=0.;
if(u_dust>.5){
vec2 q=p*14.+vec2(t*.15,-t*.25);
vec2 id=floor(q);vec2 h=hash2(id);
float dd=length(fract(q)-.5-(h-.5)*.6);
dust=smoothstep(.06,.0,dd)*step(.86,h.x)*(.5+.5*sin(t*2.+h.y*30.))*cone*fall*1.4;
}
vec3 col=mix(u_c0,u_c3,clamp(r*.7+(1.-dist/len)*.4,0.,1.)*u_dark);
float a=(v+dust)*mix(.5,1.,u_dark)*u_intensity;
a=clamp(a,0.,1.);
gl_FragColor=vec4(col*a,a);
}`;
const FALLBACK_AT: Record<LightRaysOrigin, string> = { top: '50% -12%', 'top-left': '12% -12%', 'top-right': '88% -12%' };
const FALLBACK_TURN: Record<LightRaysOrigin, number> = { top: 180, 'top-left': 156, 'top-right': 204 };
/** Still picture: a cone of striped light from the same corner */
function fallbackFor(origin: LightRaysOrigin) {
const at = FALLBACK_AT[origin];
const turn = FALLBACK_TURN[origin];
const ray = 'color-mix(in oklab, var(--primary) 40%, var(--foreground) 12%)';
return [
`repeating-conic-gradient(from ${turn - 40}deg at ${at}, transparent 0deg, color-mix(in oklab, ${ray} 30%, transparent) 2deg, transparent 5deg, transparent 9deg)`,
`radial-gradient(ellipse 60% 90% at ${at}, color-mix(in oklab, var(--primary) 26%, transparent), transparent 70%)`,
].join(', ');
}
/**
* Light rays from above behind any content. With children it renders a box,
* without children only the layer for a `relative` parent.
*/
export function LightRays({ intensity = 1, speed = 1, colors, interactive = true, origin = 'top', reach = 1, spread = 1, dust = true, fallback, scrim = 'none', scrimStrength, scrimColor, children, className, contentClassName, layerClassName }: LightRaysProps) {
const at = FALLBACK_AT[origin];
return (
<ShaderBackdrop
scrim={scrim}
scrimStrength={scrimStrength}
scrimColor={scrimColor}
className={className}
contentClassName={contentClassName}
layer={
<ShaderCanvas
fragment={FRAG}
colors={colors ?? ['var(--primary)', 'var(--primary)', 'var(--primary)', 'color-mix(in oklab, var(--primary) 35%, var(--foreground))']}
uniforms={{ u_origin: ORIGIN[origin], u_reach: reach, u_spread: spread, u_dust: dust ? 1 : 0 }}
speed={speed}
intensity={intensity}
scale={0.6}
pointer={interactive}
fallback={
fallback ?? (
<div
className="absolute inset-0"
style={{ background: fallbackFor(origin), maskImage: `radial-gradient(ellipse 90% 110% at ${at}, #000 20%, transparent 75%)`, WebkitMaskImage: `radial-gradient(ellipse 90% 110% at ${at}, #000 20%, transparent 75%)` }}
/>
)
}
className={layerClassName}
/>
}
>
{children}
</ShaderBackdrop>
);
}Frequent questions
How do I install Light rays?
With aiw add @ai-website/light-rays. The command puts components/ui/light-rays.tsx into your project, the code is yours from then on. It brings along webgl. It needs no further npm packages.
Which variants does Light rays have?
4 variants, Hero from above, Morning band, Spotlight card and Behind a call to action.
Can an AI assistant build with Light rays?
Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Light rays with aiw_components and install it with aiw_add @ai-website/light-rays.