Dot Matrix Wave
Add a dot-grid background that ripples on hover and click.
import { DotMatrixWave } from '@youj/motion/backgrounds'
Installation & usage
npm i @youj/motionFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use it for products that sell precision: design tools, developer platforms, anything about pixels and grids. It's also a good "listening" state, because its idle pulses read as something waiting for input. Don't use it behind long text or dense UI, and don't pair it with another animated background on the same page.
The matrix fills its nearest positioned ancestor and paints behind your content, so make the container relative and isolate.
import { DotMatrixWave } from '@youj/motion/backgrounds'
export function PrecisionHero() {
return (
<section className="relative isolate min-h-[560px]">
<DotMatrixWave />
<p className="relative text-5xl font-semibold">Built to the last pixel</p>
</section>
)
}Every dot is a single anti-aliased disc on a grid snapped to device pixels, evaluated at its center, so the lattice stays crisp at any pixel ratio. Untouched, the matrix breathes with a slow, low swell. Dots grow and brighten toward the excited color as a wave passes through them.
Description
A hero section over the dot matrix, which swells in the background; click anywhere in the field to send a ripple.
When nobody is interacting, the matrix sends a gentle pulse from a point in the middle of the field every few seconds of its clock. The points follow a fixed sequence, not a random one. Turn the pulses off with ambient={false} when the page already has something moving.
An empty state for a webhook endpoint, where the matrix reacts to the pointer and to the Send test event button.
With interactive on (the default), the dots under the pointer rise into a tight lens, and moving the pointer leaves a wake of soft rings, one for every 120 pixels traveled. A press anywhere in the field sends a strong ring from that point, including a press on a button that sits above the matrix.
Here the matrix is an empty state for a webhook endpoint. The ambient pulses say it's listening, and pressing Send test event sends a ring out from the button. The pointer is tracked on the window, so the matrix responds under the content above it.
Controls for the dot spacing and for the ambient pulses.
spacingis the grid pitch in CSS pixels. It defaults to 22. Smaller values read as a fine texture, and larger values as a bold graphic.intensity, from 0 to 1.5, sets the dots' contrast against the canvas.speedmultiplies the clock that drives the swell and the rings' travel. It stacks with the time scale.
colors takes two CSS colors: the dots at rest, then the excited color they heat toward. The defaults are --color-fg and --color-accent. Token references follow the theme.
Code
dot-matrix-wave-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propscolors, intensity, speed… (7)
Props
| Prop | Type | |
|---|---|---|
colors | [dots: string, excited: string][dots, excited] CSS colors. Default: | – |
intensity | numberDot contrast, 0–1.5. default 1 | 1 |
speed | numberClock multiplier for the idle swell and ripple travel. default 1 | 1 |
spacing | numberGrid pitch in CSS px. default 22 | 22 |
interactive | booleanPointer raises a lens of dots and leaves a wake; clicks send a ripple. default true | true |
ambient | booleanA pulse every few seconds while nobody is interacting, each from the next point of a fixed, evenly spread sequence across the middle of the field. default true | true |
className | string | – |
PerformanceIt's one WebGL2 fragment shader on a single full-screen triangle,…
Performance
It's one WebGL2 fragment shader on a single full-screen triangle, rendered at a device-pixel ratio capped at 2. It tracks at most eight rings at a time, and the oldest gives way to a new one. The loop pauses when the matrix is offscreen or the tab is hidden, recovers from a lost GL context, and releases the context on unmount. Without WebGL2, it falls back to a static CSS dot pattern at the same spacing.
MotionSwell, rings, pointer lens · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Swellidle breathing of the whole lattice | no token speed ÷ time scale | speed ÷ time scale | |
| Ringsclicks, the pointer wake and ambient pulses | no token 380 px per clock second | 380 px per clock second | |
| Pointer lensfollows the pointer, fades in and out | no token smoothed per frame | smoothed per frame |
Reduced motion
Draws one still frame with a single ring frozen mid-flight, so the matrix still reads as something that responds. There's no lens, no wake and no ambient pulse.
Ring travel is measured in the matrix's own clock, so it slows with the Speed control like everything else.
Reduced motion
The matrix holds one composed frame, with a single ring frozen in the middle of the field. There's no swell, no lens and no pulse, but the lattice and its colors stay the same, and the frozen ring still hints at what the matrix does. Switch on Reduced motion in any preview above to compare.
AccessibilityARIA
Accessibility
- It's decorative: the element is
aria-hiddenand ignores pointer events, so presses pass through to the content above it. - A ring is visual feedback only. If a press does something, like sending a test event, confirm it in text as well. The Pointer and clicks story does this with a status message.
- Keep a quiet pool of canvas behind the copy, and check your text's contrast over the brightest rings.