WebberUI

Liquid Metal

A reflective liquid chrome / flowing metal surface drawn live on a 2D canvas, with specular striping and optional mouse ripples.

Flowing liquid metal simulated by a domain-warped multi-layer sine field, mapped onto a three-stop color ramp with sharpened specular bands layered over it, drawn on a low-resolution buffer and then smoothly upscaled by CSS.

Loading preview…
npx shadcn@latest add https://webberui.com/r/liquid-metal.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1
3
0.6
3
<LiquidMetal />

Installation

npx shadcn@latest add https://webberui.com/r/liquid-metal.json

Or, once registries are configured in components.json, install it as @webberui/liquid-metal.

Usage

Put your foreground content in children, and write the layout (centering, height) on className:

import { LiquidMetal } from "@/components/ui/liquid-metal";

<LiquidMetal className="flex h-[400px] items-center justify-center rounded-2xl">
  <h1 className="text-4xl font-bold text-white mix-blend-difference">
    Chrome
  </h1>
</LiquidMetal>

Custom color ramp, mouse ripples enabled, and a faster flow:

<LiquidMetal
  colors={["#1a1005", "#b45309", "#fef3c7"]}
  specular="#fffbeb"
  interactive
  speed={1.4}
  distortion={0.8}
  className="flex h-[320px] items-center justify-center rounded-2xl"
>
  <p className="text-lg text-white mix-blend-difference">Molten gold</p>
</LiquidMetal>

Props

PropTypeDefaultDescription
colors[string, string, string]Chrome silver paletteThree metal color stops: [shadow, midtone, highlight]
specularstring"#f8fafc"Color of the specular reflection bands
speednumber1Flow speed multiplier
scalenumber3Spatial frequency — higher makes the surface texture denser
distortionnumber0.6Domain warp strength — higher makes it feel more liquid
reflectionsnumber3Density of the specular bands
interactivebooleanfalseThe mouse drags ripples across the surface (touch has no effect)
resolutionnumber192Width of the internal drawing buffer — smaller is cheaper
childrenReact.ReactNodeForeground content, layered above the metal surface
classNamestringAppended to the outermost container's className

How it works

  • Low-resolution buffer + CSS upscaling. The per-pixel math runs on a discrete buffer of fixed width (192 by default); after putImageData, CSS stretches the canvas to fill the container, and the browser's bilinear interpolation naturally smears the pixels into a smooth liquid surface — the cost is independent of the container's actual size
  • The surface is a continuous field built from three stacked sine layers, with the coordinates themselves displaced by another sine (domain warping) to produce the offset flow; the specular striping is a high-frequency sine of the field value sharpened with pow4, layered over the three-stop ramp to give chrome its reflection
  • The buffer's aspect ratio follows the container through a ResizeObserver, so the metal texture never gets stretched out of shape; on teardown, cancelAnimationFrame and disconnect are cleaned up together
  • Props that can change are stored in refs and the animation loop reads the latest value each frame, so changing a parameter never restarts requestAnimationFrame
  • With interactive on, the cursor position is normalized to 0~1 and injects a ring-shaped ripple at that point with a Gaussian falloff over distance; it only applies when pointerType === "mouse", and the ripple fades out once the cursor leaves
  • When the user has "reduce motion" enabled at the system level, only a single static metal surface is drawn and the animation loop never starts

On this page