Hover an image and it distorts like the surface of water. The ripple ramps in, follows your cursor speed, and settles with a damped wave when you leave. Hover the images below.
pnpm dlx shadcn@latest add "https://godui.design/r/lab/liquid-image.json"
LiquidImage distorts the image with an SVG feTurbulence + feDisplacementMap
filter, which every modern browser supports. The displacement scale is eased
toward its target in a single requestAnimationFrame loop and written straight
to the filter, so the image itself never re-renders. Cursor velocity intensifies
the ripple; leaving the image lets it settle back with a damped wave.
import { LiquidImage } from "@/components/godui/liquid-image";
<LiquidImage src="/photo.jpg" alt="Mountain" className="aspect-square" />
Set trigger="always" for a continuously breathing ripple, independent of hover.
<LiquidImage src="/photo.jpg" alt="Mountain" trigger="always" />
strength is the peak displacement in pixels; frequency controls the ripple
scale. Smaller values give larger, smoother ripples.
<LiquidImage src="/photo.jpg" alt="Mountain" strength={40} frequency={0.008} />
When prefers-reduced-motion is set, or SVG filter: url() is unavailable,
LiquidImage falls back to a gentle CSS scale-and-brighten on hover. Always pass a meaningful alt.
| Prop | Type | Default | Description |
|---|
src | string | none | Image source. |
alt | string | none | Accessible description of the image. |
strength | number | 28 | Peak ripple displacement on interaction, in px. |
frequency | number | 0.012 | Turbulence frequency. Smaller gives larger, smoother ripples. |
trigger | "hover" | "always" | "hover" | When the ripple is active. |
imgClassName | string | none | Classes for the inner <img> (sizing, aspect, object-fit). |
LiquidImage also forwards every standard <div> attribute to the root element.