Copy URL or Code
Paste to your AI coding assistant and say:
“Add this liquid metal effect to my submit button”
Done. Your AI handles the rest.
Fully customizable. Ask your AI to adjust the size, border radius, grain texture intensity, or change the button text.
/*
================================================================================
AI COMPONENT: LiquidMetal
================================================================================
SETUP:
1. Install: npm install @paper-design/shaders
2. Create file: components/LiquidMetal.tsx
3. Copy this entire code block into that file
4. Import: import LiquidMetal from "@/components/LiquidMetal"
================================================================================
QUICK CUSTOMIZATION (via props)
================================================================================
| Prop | Type | Default | Description |
|-------------|-----------------|---------|-----------------------------------|
| size | number | 385 | Size of the component in pixels |
| repetition | number | 1.5 | Pattern repetition count |
| softness | number | 0.5 | Edge softness (0-1) |
| shiftRed | number | 0.3 | Red color shift (0-1) |
| shiftBlue | number | 0.3 | Blue color shift (0-1) |
| distortion | number | 0 | Distortion amount |
| contour | number | 0 | Contour intensity |
| angle | number | 100 | Rotation angle in degrees |
| scale | number | 1.5 | Pattern scale |
| shape | number | 1 | 0 = linear, 1 = circular |
| offsetX | number | 0.1 | X position offset |
| offsetY | number | -0.1 | Y position offset |
| pixelRatio | number | 0.6 | Render quality (lower = faster) |
| showOutline | boolean | true | Show decorative ring |
| icon | React.ReactNode | - | Custom center icon |
| children | React.ReactNode | - | Content overlay |
| className | string | "" | Additional CSS classes |
================================================================================
COLOR PRESETS
================================================================================
WARM METAL (orange/gold tones):
shiftRed={0.6} shiftBlue={0.1} angle={45}
COOL METAL (blue/purple tones):
shiftRed={0.1} shiftBlue={0.6} angle={180}
NEUTRAL METAL (balanced):
shiftRed={0.3} shiftBlue={0.3} angle={100}
================================================================================
COMMON MODIFICATIONS
================================================================================
1. LARGER SIZE:
<LiquidMetal size={500} />
2. WARM COLOR PALETTE:
<LiquidMetal shiftRed={0.6} shiftBlue={0.1} angle={45} />
3. WITHOUT DECORATIVE RING:
<LiquidMetal showOutline={false} />
4. WITH CUSTOM ICON:
<LiquidMetal icon={<MyIcon className="w-12 h-12" />} />
5. HIGHER QUALITY (slower):
<LiquidMetal pixelRatio={1} />
6. MORE PATTERN DETAIL:
<LiquidMetal repetition={3} scale={2} />
================================================================================
SOURCE CODE
================================================================================
*/
"use client";
import React, { useEffect, useRef } from "react";
import {
liquidMetalFragmentShader,
ShaderMount,
} from "@paper-design/shaders";
export interface LiquidMetalProps {
repetition?: number;
softness?: number;
shiftRed?: number;
shiftBlue?: number;
distortion?: number;
contour?: number;
angle?: number;
scale?: number;
shape?: number;
offsetX?: number;
offsetY?: number;
pixelRatio?: number;
className?: string;
size?: number;
children?: React.ReactNode;
showOutline?: boolean;
icon?: React.ReactNode;
}
export function LiquidMetal({
repetition = 1.5,
softness = 0.5,
shiftRed = 0.3,
shiftBlue = 0.3,
distortion = 0,
contour = 0,
angle = 100,
scale = 1.5,
shape = 1,
offsetX = 0.1,
offsetY = -0.1,
pixelRatio = 0.6,
className = "",
size = 385,
children,
showOutline = true,
icon,
}: LiquidMetalProps) {
const containerRef = useRef<HTMLDivElement>(null);
const shaderMountRef = useRef<ShaderMount | null>(null);
useEffect(() => {
if (!containerRef.current) return;
shaderMountRef.current = new ShaderMount(
containerRef.current,
liquidMetalFragmentShader,
{
u_repetition: repetition,
u_softness: softness,
u_shiftRed: shiftRed,
u_shiftBlue: shiftBlue,
u_distortion: distortion,
u_contour: contour,
u_angle: angle,
u_scale: scale,
u_shape: shape,
u_offsetX: offsetX,
u_offsetY: offsetY,
},
undefined,
pixelRatio
);
return () => {
if (shaderMountRef.current) {
shaderMountRef.current.dispose();
shaderMountRef.current = null;
}
};
}, [repetition, softness, shiftRed, shiftBlue, distortion, contour, angle, scale, shape, offsetX, offsetY, pixelRatio]);
return (
<div
className={`liquid-metal-container ${className}`}
style={{
position: "relative",
width: size,
height: size,
borderRadius: "50%",
}}
>
<div
ref={containerRef}
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: size * 0.91,
height: size * 0.91,
borderRadius: "50%",
overflow: "hidden",
}}
/>
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: size * 0.91,
height: size * 0.91,
background: "linear-gradient(#444, #000)",
borderRadius: "50%",
boxShadow: "inset 0 2px 2px 2px rgba(255, 255, 255, 0.3)",
pointerEvents: "none",
mixBlendMode: "overlay",
}}
/>
{showOutline && (
<div
className="outline-ring"
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
justifyContent: "center",
alignItems: "center",
width: size * 1.013,
height: size * 1.013,
borderRadius: "50%",
zIndex: 1,
}}
>
<div
className="outline-border"
style={{
position: "absolute",
inset: 0,
padding: 2,
borderRadius: "inherit",
background: "conic-gradient(from 180deg, blue, purple, red, purple, blue)",
filter: "grayscale(1)",
WebkitMask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
WebkitMaskComposite: "xor",
mask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
maskComposite: "exclude",
transition: "filter 0.3s ease",
}}
/>
{icon ? (
<div style={{ zIndex: 2 }}>{icon}</div>
) : (
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" style={{ width: size * 0.26, height: size * 0.26, fill: "#65615f", transform: "rotate(-45deg)", zIndex: 2 }}>
<path d="M843.968 896a51.072 51.072 0 0 1-51.968-52.032V232H180.032A51.072 51.072 0 0 1 128 180.032c0-29.44 22.528-52.032 52.032-52.032h663.936c29.44 0 52.032 22.528 52.032 52.032v663.936c0 29.44-22.528 52.032-52.032 52.032z" />
<path d="M180.032 896a49.92 49.92 0 0 1-36.48-15.616c-20.736-20.8-20.736-53.76 0-72.832L807.616 143.616c20.864-20.8 53.76-20.8 72.832 0 20.8 20.8 20.8 53.76 0 72.768L216.384 880.384a47.232 47.232 0 0 1-36.352 15.616z" />
</svg>
)}
</div>
)}
{children && (
<div style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", zIndex: 10, display: "flex", alignItems: "center", justifyContent: "center" }}>
{children}
</div>
)}
<style jsx>{`
.liquid-metal-container:hover .outline-border {
filter: grayscale(0) !important;
}
`}</style>
</div>
);
}
export default LiquidMetal;import LiquidMetal from "@/components/LiquidMetal";
// Basic usage
export default function MyComponent() {
return (
<div className="flex items-center justify-center h-screen bg-gray-900">
<LiquidMetal />
</div>
);
}
// With custom size and colors
export function WarmMetal() {
return (
<LiquidMetal
size={400}
shiftRed={0.6}
shiftBlue={0.1}
angle={45}
/>
);
}
// As a button with custom icon
export function MetalButton() {
return (
<button className="cursor-pointer">
<LiquidMetal
size={200}
icon={
<svg className="w-8 h-8 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
}
/>
</button>
);
}
// Without decorative ring
export function MinimalMetal() {
return (
<LiquidMetal
size={300}
showOutline={false}
/>
);
}