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