KQ-1421 · INK/SPLASH

Ink Splash

A pool of water under your content, simulated on the GPU as a real incompressible fluid (advection, vorticity confinement and a warm-started pressure solve every frame): the pointer drags a brush through it that pushes the water and lays pigment, so ink is shoved ahead, peels into curls, diffuses and fades. A click drops a splash — a zero-mean volume source pushes a ring of water out from the drop, its ink blooms with a pooled rim, satellite droplets patter down on a cascade stagger and a surface ripple runs out bending the ink — all inside 600 ms. Keyboard focus moving between controls inside draws a stroke of ink to the new one, and Enter or Space on a control drops a splash there.

KQ-1421 · INK/SPLASH

Basinworks · ink studio

Stir the pool. Leave a mark.

Inks that move like water, for brush and screen.

still water · move to stir, click to drop

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/ink-splash

Props

children
React.ReactNode
default —
Content floated on the pool, such as a headline. It keeps every click, focus and key.
viscosity
number
default 0.3
How thick the water is, 0 to 1: thin water coasts and curls, thick ink stops short and smooths.
fade
number
default 6
How long ink lingers before the water clears, in seconds (1 to 20).
colors
"indigo" | "sumi" | "tide" | "ember" | readonly string[]
default "indigo"
The pigments: a named set from the theme tokens, or your own CSS colours. The brush walks through them, one per stroke and per splash.
curl
number
default 0.5
How strongly eddies are kept and sharpened, 0 to 1 (thick water damps them further).
radius
number
default 22
The brush's width, in px.
force
number
default 0.6
How hard the brush pushes the water, 0 to 1.
opacity
number
default 0.85
Coverage of the densest ink, 0 to 1.
splash
boolean
default true
A click (or Enter/Space on a control inside) drops a splash.
splashSize
number
default 46
The splash drop's radius, in px.
droplets
number
default 7
Satellite droplets that patter down around each splash, 0 to 12.
ambient
boolean
default true
With nobody stirring, a slow drip lands every few seconds so the pool stays alive.
interactive
boolean
default true
The pointer stirs the pool (mouse and pen; a tap drops a splash).
followFocus
boolean
default true
Keyboard focus moving between controls inside draws a stroke of ink to the new one.
paused
boolean
default false
Hold the water still and stop the frame loop.
resolution
number
default 128
The simulation grid's long side, 48 to 192 cells. Lower is cheaper and softer.
dpr
number
default 2
The highest device pixel ratio the canvas draws at (never above 2).
seed
number
default 11
Seed for the opening drop, the drips, the droplets, the grain and the still.
onSplash
(splash: { x: number; y: number; source: "pointer" | "keyboard" | "ambient" }) => void
default —
Fires when a drop lands, with where (px from the box's top-left) and why.
className
string
default —
Classes for the box. Give it a height; children are laid out inside it.