Skip to content

Ripple Pond

Show a calm dot lattice where moving and clicking drop ripples that spread and fade.

import { RipplePond } from '@youj/motion/backgrounds'

Experimental: what this label means

Installation & usage

npm i @youj/motion

For 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.

Reference

Propscolors, intensity, speed… (7)

Props

RipplePond props
PropType
colors[dots: string, wave: string]

[dots, wave] CSS colors. Default: --color-fg-subtle dots that take --color-accent on a crest.

–
intensitynumber

Dot opacity and wave height, 0–1.5.

default 1

1
speednumber

Clock multiplier for how fast the rings travel and fade.

default 1

1
spacingnumber

Lattice pitch in CSS px.

default 16

16
ambientboolean

A drop every few seconds while nobody is interacting.

default true

true
interactiveboolean

Moving the pointer drops soft ripples; a press drops a strong one.

default true

true
classNamestring–
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

Motion tokens
MomentTokens
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-hidden and 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 ambient on, a drop falls every few seconds. Turn it off where even that is too much. Under reduced motion nothing moves at all.