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