Skip to content
UI Kit
Accent color

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.

4 variantsOne of 6 components in 3D backgrounds

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

Hero from aboveNew in 1.2

Light falls straight onto a centered headline, like through a skylight.

Morning bandNew in 1.2

Light slants in from the upper right across a band of figures.

Spotlight cardNew in 1.2

A narrow beam lights the one card that matters most.

Behind a call to actionNew in 1.2

A wide, slow fan of light opens over the closing panel.

Details

What Light rays brings along, read from the registry and the source.

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

Props

The LightRaysProps interface from components/ui/light-rays.tsx, with the comments from the code. Also takes every prop of ShaderLayerProps.

  • originoptional
    LightRaysOrigin

    toptop-lefttop-right

    Where the light comes from

  • reachoptional
    number

    How far the rays reach down, 0.3 to 1.5, default 1

  • spreadoptional
    number

    Opening of the cone, 1 is the designed width

  • dustoptional
    boolean

    Floating 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
components/ui/light-rays.tsx
'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.

Often searched

Jump to

Copied to the clipboard