KQ-1404 · HALO/RINGS

Halo Rings

Rings of light ripple out from a source that trails the pointer, drawn in one WebGL2 pass under your content: each ring spreads, thins and fades, and because every ring is a short wave packet summed as a wave, offset rings that cross add and cancel into a fine lattice. The source follows the pointer on a damped spring blended between springs.glide and springs.drift by `lag`, so a sweep leaves a wake of crossing rings, and a press sends a bright ring that cuts through the rest. Keyboard focus moves the source to the focused control and Enter or Space on it sends the bright ring.

KQ-1404 · HALO/RINGS

Waylight Radio · on air

Live from the reading room.

Quiet voices, late records, 88.4 across the valley.

on air · 1.2 rings a second under the cursor

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/halo-rings

Props

pulse
number
default 1.2
Rings sent per second while the pointer is in; three quarters of that at rest.
width
number
default 8
A new ring's width in px; it thins as it spreads.
colors
"tide" | "dusk" | "ember" | "mono" | string[]
default "tide"
A named palette, or two CSS colours a ring travels between as it ages; resolved on the element so they follow the theme.
speed
number
default 120
How fast a ring spreads, in px per second.
reach
number
default 300
How far a ring travels before it has faded out, in px, never past the box's diagonal.
lag
number
default 0.5
How far the source trails the pointer, from springs.glide at 0 to springs.drift at 1.
interference
number
default 0.6
From plain glowing rings at 0 to wave packets whose crests add and cancel where they cross at 1.
intensity
number
default 0.7
How bright the rings are, 0 to 1.
origin
[number, number]
default [0.5, 0.5]
Where the source rests with nobody pointing, as x and y fractions of the box.
ambient
boolean
default true
Keep pulsing from origin while nobody points; off, the rings die away and the loop stops.
interactive
boolean
default true
Follow the pointer and keyboard focus; a press sends a bright ring.
layer
"under" | "over"
default "under"
Draw the rings under the content or over it.
paused
boolean
default false
Freeze the rings where they are.
dpr
number
default 2
The highest device pixel ratio the canvas draws at (never above 2).
onRing
(point: { x: number; y: number }) => void
default —
Fires when a press or a keyboard activation sends a bright ring, with its point in the box.
className
string
default —
Classes for the box; size it here.
children
React.ReactNode
default —
Content over (or under) the rings; it keeps every click, focus and key.