Backgrounds

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.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add liquid-metal.en

New project or installation trouble? Start here →

Liquid Metal is a React liquid metal (chrome) background component that draws a flowing metallic surface pixel by pixel on a canvas 2D context. The wrapper is styled with Tailwind CSS, no WebGL or shaders are involved, and Motion is used only to detect the reduced-motion setting.

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
Active edited usageDefault props
<LiquidMetal/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • The hero background of a product launch page, behind a large headline.
  • Sections introducing hardware, audio gear, or a premium plan that call for a metallic backdrop.
  • Event or countdown banners, with colors switched to gold or copper tones.
  • Small decorative areas the size of a card or button, with a lower resolution to save more work.

Installation

npx webberui@latest add https://webberui.com/r/liquid-metal.en.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>

Examples

Hero background

Use LiquidMetal as the section container, put the headline and button in children, and set the layout in className.

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

export function LaunchHero() {
  return (
    <LiquidMetal className="flex min-h-[480px] flex-col items-center justify-center gap-6 px-6 text-center">
      <h1 className="text-5xl font-semibold text-white mix-blend-difference">
        The next audio interface
      </h1>
      <a href="/preorder" className="rounded-full bg-white px-5 py-2 text-sm font-medium text-black">
        Join the preorder list
      </a>
    </LiquidMetal>
  );
}

Copper palette with finer texture

colors is shadow, midtone, and highlight in that order; a higher scale makes the texture denser and a lower reflections gives fewer bright bands.

<LiquidMetal
  colors={["#1c0f0a", "#9a3412", "#fed7aa"]}
  specular="#fff7ed"
  scale={5}
  reflections={2}
  speed={0.6}
  className="h-[280px] rounded-2xl"
/>

Small card at a lower resolution

resolution is the width in px of the internal drawing buffer; a small card can use a lower value, and CSS scales the result up smoothly.

<LiquidMetal resolution={96} className="flex h-40 w-72 items-end rounded-xl p-4">
  <span className="text-sm font-medium text-white">Pro plan</span>
</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.ReactNode—Foreground content, layered above the metal surface
classNamestring—Appended 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

FAQ

Does it work with the Next.js App Router?

Yes. The source already has "use client", so import it from a Server Component page directly; the canvas is only drawn inside an effect, and the server renders an empty container.

Does it keep rendering after it scrolls out of view?

No. An IntersectionObserver cancels the requestAnimationFrame loop when the canvas leaves the viewport and restarts it when it comes back. To lower the cost further, reduce resolution.

Can colors use rgb() or Tailwind color names?

No. colors and specular only parse hex colors in #rgb or #rrggbb form; anything else falls back to a neutral gray.

Does the ripple work on phones?

No. The interactive ripple only responds when pointerType is mouse; touch input is ignored.

What happens when the user prefers reduced motion?

A single static metal surface is drawn and the animation loop never starts.