Ripple Pond
Show a calm dot lattice where moving and clicking drop ripples that spread and fade.
import { RipplePond } 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 where the idea is calm with a response: a sign-in page, a settings landing, a quiet empty state. It does nothing loud on its own, so it suits pages that should feel still until someone touches them. Keep it to one per page.
The pond fills its nearest positioned ancestor and paints behind your content, so make the container relative and isolate.
import { RipplePond } from '@youj/motion/backgrounds'
export function CalmHero() {
return (
<section className="relative isolate min-h-[560px]">
<RipplePond />
<p className="relative text-5xl font-semibold">Every action leaves a ripple</p>
</section>
)
}Moving the pointer drops a soft ripple every 110 pixels of travel. A press or a tap drops a strong one right where it lands, which is what makes the pond work on touch screens. Each ring travels outward as a wave packet with wavelets trailing it, and fades over about three seconds. With ambient on (the default), an idle pond lets a drop fall every few seconds, each from the next point of a fixed spread across the field. Turn it off where the pond sits behind something people work in.
Description
A hero section over the pond; move the pointer across it to leave a wake, or click to drop a stone.
colors takes two CSS colors: the dots at rest, then the color they take on a crest. The defaults are --color-fg-subtle and --color-accent. Token references follow the theme, and the pond rereads them when the theme changes.
A switch between three lattice pitches.
spacing is the distance between dots in CSS pixels. A finer pitch resolves the ring and its trailing wavelets more clearly and draws more dots; a wide one reads as a loose, graphic texture. The ripples keep their size and speed in pixels, so only the sampling changes.
Code
ripple-pond-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propscolors, intensity, speed… (7)
Props
| Prop | Type | |
|---|---|---|
colors | [dots: string, wave: string][dots, wave] CSS colors. Default: | – |
intensity | numberDot opacity and wave height, 0–1.5. default 1 | 1 |
speed | numberClock multiplier for how fast the rings travel and fade. default 1 | 1 |
spacing | numberLattice pitch in CSS px. default 16 | 16 |
ambient | booleanA drop every few seconds while nobody is interacting. default true | true |
interactive | booleanMoving the pointer drops soft ripples; a press drops a strong one. default true | true |
className | string | – |
PerformanceCanvas 2D; work follows the rings, not the lattice
Performance
It's drawn with Canvas 2D, not WebGL. Each ripple only visits the dots near its ring, so the cost follows the number of live ripples (at most 28) and not the size of the lattice, and the dots are batched into nine paths by wave height. The canvas renders at a device-pixel ratio capped at 2. On the low motion-budget tier the pitch widens by 30%, which keeps about 60% of the dots, and the loop runs at 30 fps. The loop pauses when the pond is offscreen or the tab is hidden, and it stops and cleans up on unmount. If the browser can't create a 2D context, it falls back to a static CSS wash of the accent color.
MotionRings, wake, ambient drops · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Ringa wave packet with trailing wavelets, losing height as it spreads | no token speed ÷ time scale | speed ÷ time scale | |
| Wakea soft ripple every 110px the pointer travels | no token distance-based | distance-based | |
| Pressa strong ripple where it lands | no token on press | on press | |
| Ambient dropone every few seconds while nobody is interacting | no token clock-based | clock-based |
Reduced motion
Draws one still pond with two rings caught mid-spread: the same composition every time. Nothing moves and nothing responds, and it redraws only when the size, the props or the theme change.
Reduced motion
The pond shows two rings frozen mid-spread, so it's the same on every visit and in every screenshot, and it still shows what a ripple looks like. Only the travel, the wake and the ambient drops are gone. Switch on Reduced motion in any preview above to compare.
AccessibilityARIA
Accessibility
- It's decorative: the element is
aria-hiddenand ignores pointer events. - The lattice is faint at rest, and crests brighten it, so check your text's contrast where a ripple can pass behind it, or lower
intensity. - With
ambienton, a drop falls every few seconds. Turn it off where even that is too much. Under reduced motion nothing moves at all.