Metal Button

PreviousNext

Animated liquid metal ring around the shadcn Button — text and icon variants powered by MetalFx.

Liquid metal

Button + animated metal ring

className targets the shadcn Button; metalFxClassName styles the MetalFx wrapper. Use preset, metalVariant, and paused to tune the effect.

Button variants

Outline and secondary read well against the ring; default adds a stronger fill.

Metal preset

metal-fx shares one WebGL palette for the whole page — pick a preset, then watch the ring update on the preview below.

Metal variant

button is a pill ring; circle uses a thicker ring suited to compact controls.

Strength

Strength scales shader opacity and glow; the animation keeps running underneath.

Icon buttons

Icon buttons default to icon sizing and circle variant. The Wand icon disables the halo glow.

Interactive

Toggle the shader without hiding the button. Respects prefers-reduced-motion. Dark-mode reflections hit the chip ref.

Tip: pause freezes the ring on the last frame; the button stays clickable.

Installation

pnpm dlx shadcn@latest add https://cult-ui.com/r/metal-button.json

Usage

MetalButton wraps the standard shadcn Button with MetalFx from the metal-fx package. All visible instances on a page share one WebGL renderer, so multiple buttons stay efficient.

  • className — styles the inner Button (padding, gap, typography).
  • metalFxClassName / metalFxStyle — styles the MetalFx wrapper (surface fill when normalizeHostStyles is on).
  • MetalIconButton — same API with size="icon-sm" and metalVariant="circle" by default.
import { MetalButton, MetalIconButton, } from "@/components/ui/metal-button" import { Sparkles } from "lucide-react"
<MetalButton type="button" variant="outline" preset="chromatic"> Continue </MetalButton> <MetalIconButton type="button" variant="outline" aria-label="Sparkles" preset="gold" > <Sparkles className="size-3.5" /> </MetalIconButton>

Presets and theme

Pick a bundled palette with preset: "chromatic" (default), "silver", or "gold". Each preset includes dark and light tunings; theme ("auto" | "dark" | "light") selects which side to use. Because metal-fx shares one palette per page, changing preset on any instance updates every visible ring.

<MetalButton preset="gold" theme="dark" variant="default"> Upgrade </MetalButton>

Ring shape and intensity

  • metalVariant"button" (pill ring, default) or "circle" (thicker ring for compact controls).
  • strength01; scales shader opacity and glow while the animation keeps running (default 0.9).
  • paused — freezes the ring on the last frame; the button stays fully clickable.
<MetalButton metalVariant="circle" strength={0.75} paused={isPaused} variant="outline" > Settings </MetalButton>

Styling split: button vs wrapper

With normalizeHostStyles (default true), variant fills and hover colors apply on the MetalFx wrapper so the inner button stays transparent and the liquid ring stays visible. Pass normalizeHostStyles={false} if you want full shadcn chrome on the button itself (filled variants will cover most of the metal).

<MetalButton className="gap-2 pl-6 pr-5" metalFxClassName="shadow-sm" variant="outline" > Get started </MetalButton>

Reflections and glow

In dark theme, pass reflectionTargets — an array of refs to sibling elements — for a soft proximity reflection on nearby chips or labels. Use disableGlow to turn off the wandering halo while keeping the shader ring.

const chipRef = useRef<HTMLButtonElement>(null) <> <button ref={chipRef} type="button" className="..."> Tools </button> <MetalButton reflectionTargets={[chipRef]} variant="outline" > Send </MetalButton> </>

API Reference

Exports

ExportDescription
MetalButtonText button with liquid metal ring
MetalIconButtonIcon-sized control; defaults to size="icon-sm" and metalVariant="circle"
MetalButtonPropsProps for MetalButton
MetalIconButtonPropsAlias of MetalButtonProps

MetalButton accepts all Button props plus MetalFx shell props below. The ref attaches to the MetalFx wrapper (HTMLDivElement).

MetalFx props (forwarded)

PropTypeDefaultDescription
preset"chromatic" | "silver" | "gold""chromatic"Bundled shader palette (shared page-wide)
theme"auto" | "dark" | "light""auto"Resolves preset tuning from OS or pins a mode
metalVariant"button" | "circle""button"Ring shape and thickness baseline
strengthnumber0.9Effect opacity and glow (01)
pausedbooleanfalseFreeze ring on last frame
borderRadiusnumberOverride computed child radius (CSS px)
normalizeHostStylesbooleantrueMove variant fill to wrapper; strip button chrome
disableGlowbooleanfalseDisable halo; ring still renders
reflectionTargetsRefObject<HTMLElement | null>[]Dark-mode proximity reflections on siblings
shaderScalenumbervariant defaultZoom into shared shader pattern
ringCssPxnumbervariant defaultRing thickness in CSS pixels
scalenumber1Master multiplier for ring, glow, and reflections
metalFxClassNamestringClass names on the MetalFx wrapper
metalFxStyleCSSPropertiesInline styles on the MetalFx wrapper

See the MetalFx documentation for shader behavior, performance notes, and advanced tuning.

Accessibility

  • Prefer MetalIconButton with a visible aria-label (and optional title) for icon-only actions.
  • Respect prefers-reduced-motion: pause the effect when users request reduced motion so the ring does not animate unnecessarily.
  • The inner control remains a native button (or your polymorphic choice via Button props); keyboard activation works as usual.

Performance

  • One shared WebGL canvas drives every MetalFx instance on the page — prefer a single preset per view when possible.
  • Reflection scanning runs only in dark mode and only for refs you pass in reflectionTargets.
  • Use paused on off-screen or inactive controls to avoid painting frames users cannot see.

Credits

The liquid metal shader and metal-fx React wrapper are by Jakub Antalík. Documentation, live demos, and the underlying effect are at metal.jakubantalik.com.