GodUIGodUI
85

Anatomy of Light Rays

How a top glow, a repeating-conic fan, a ±6° breathe, and optional film grain stack into volumetric god-rays — all pure CSS.

LightRays is three stacked layers and one keyframe. No canvas, no rAF — just paint, an oversized transform, and an optional SVG noise overlay. Drop it first in a relative overflow-hidden container.

Three plates, one pivot

Read left → right. Every plate shares the same top-center origin:

  1. Source — soft ellipse parked just above the frame (50% -5%).
  2. Wedges — sharp repeating-conic-gradient so you can count spokes (step = 360 / rayCount, default 14).
  3. Soft fan — those wedges at inset-[-50%], blur(8px), faded with a bottom mask. Oversizing is why a ±6° rotate never flashes an empty edge.
tsx
 
const step = 360 / rayCount;
const rayColor = `color-mix(in oklab, ${color}, transparent 55%)`;

repeating-conic-gradient(
  from ${angle}deg at 50% 0%,
  transparent 0deg,
  ${rayColor} ${step * 0.16}deg,
  transparent ${step}deg,
)

Color defaults to var(--primary). Intensity (0.6 default) sets the fan opacity.

Anatomyglow · wedges · soft fan
1 · source
2 · wedges
3 · soft

Same top pivot in every plate. Plate 3 is plate 2 oversized, blurred, and faded — that's the volumetric look.

Source
ellipse glow parked just above the top edge
Wedges
repeating-conic · one spoke every 360° / rayCount
Soft fan
inset −50% · blur(8px) · mask fades downward

The sweep is tiny on purpose

Rest vs live. The live plate runs animate-light-rays-sweep:

css
 
@keyframes light-rays-sweep {
  0%   { transform: rotate(calc(var(--rays-angle) - 6deg)) scale(1);
         opacity: calc(var(--rays-intensity) * 0.7); }
  100% { transform: rotate(calc(var(--rays-angle) + 6deg)) scale(1.08);
         opacity: var(--rays-intensity); }
}
/* duration: var(--rays-speed, 14s) = 14 / speed · ease-in-out alternate */
tsx
 
style={{ "--rays-speed": `${14 / speed}s`, "--rays-angle": `${angle}deg`, … }}

The needle in the diagram tracks that same arc so the motion is not lost in the blur. motion-reduce:animate-none freezes the fan — glow and wedges stay.

Sweep±6° rotate · scale 1→1.08 · alternate
rest · 0°
sweep · ±6°

The needle tracks the same keyframe as the fan: rotate ±6°, scale up to 1.08, opacity breathes. Duration 14s / speed, alternate forever — frozen under motion-reduce.

Transform
rotate(±6deg) scale(1→1.08)
Timing
ease-in-out alternate · --rays-speed

Grain is an overlay, not a texture bake

Clean fan → feTurbulence field → both with mix-blend-overlay. Default grain={0.05} is almost subliminal; set 0 to omit the SVG entirely.

tsx
 
<feTurbulence
  type="fractalNoise"
  baseFrequency="0.9"
  numOctaves={2}
  stitchTiles="stitch"
/>
// rect opacity = grain, mix-blend-overlay
Grainclean · noise · overlay
clean
feTurbulence
overlay

Default grain={0.05} is subtle on purpose — bump it in the Result to feel the film grit. Set 0 to drop the SVG layer entirely.

Noise
fractalNoise · baseFrequency 0.9
Blend
mix-blend-overlay · opacity = grain

The result

Resultthe real component — watch the fan breathe

Three layers, one keyframe, optional grit — compositor-only the whole way.

On this page

Built with GodUI

Beautifully crafted motion components for modern interfaces.

Star on GitHub