Toggle Sidebar B
AppearanceLight & dark mode D

Metal FX

Wraps a button or icon in an animated liquid-metal ring. One shared WebGL renderer drives every instance on the page. Vue port of metal-fx by Jakub Antalik, MIT licensed.

Default

Wrap a single element. MetalFx measures it every frame, becomes the visible surface, and strips the child's own border, background and shadow so the ring is the only edge.

Dark previewReplay preview

In a composer

The send button as a gold circle, with the neighbouring chip picking up a soft reflection of the metal. Reflections only render on dark surfaces.

Dark previewReplay preview

Build anything...

Agent Auto

In a toolbar

A chromatic pill between a search field and an icon button, both of which are passed as reflection targets.

Dark previewReplay preview

Variants

button is the pill baseline (1px ring, shader scale 1.6); circle is tuned for compact icon buttons (2px ring, scale 1.3) and always resolves a true circle.

Dark previewReplay preview
variant="button"
variant="circle"

Color presets

Three palettes, each with its own dark and light tuning. Because one WebGL renderer feeds every instance, the preset is page-global: the most recently applied value wins, so switch it rather than rendering several at once.

Dark previewReplay preview

Strength

strength scales the composited alpha of the shader and glow between 0 and 1. It is per instance and never touches the wrapped content.

Dark previewReplay preview
strength 1
strength 0.6
strength 0.3

Theme

auto follows the nearest data-theme attribute or dark/light class and updates live when the page theme flips. Pin it with light or dark for a fixed surface. Like preset, the resolved theme feeds the shared shader.

Dark previewReplay preview

Paused

paused freezes this instance on its last painted frame, so the metal silhouette stays on screen. The shared loop keeps running for any other instance.

Dark previewReplay preview

Around a Button

The wrapped child can be any element, including the library Button. Give it an explicit size and a rounded shape so the ring has geometry to follow.

Dark previewReplay preview

Playground

Every knob on one panel. Type and color use Toggle Group, strength uses Slider Ruler, and the button freezes the animation.

Dark previewReplay preview
Type
Color
Options
Strength
Strength0.9
<MetalFx preset="chromatic" :strength="0.9" :reflection-targets="[searchRef]">

API Reference

MetalFx

PropTypeDefaultDescription
variant"button" | "circle""button"Shader sampling scale and ring thickness. The visible size always comes from the wrapped child.
preset"chromatic" | "silver" | "gold""chromatic"Color palette. Applied to the shared renderer, so it is page-global.
theme"auto" | "dark" | "light""auto"auto resolves from an ancestor data-theme or dark/light class, then from prefers-color-scheme.
strengthnumber1Composited alpha of the shader and glow, 0 to 1. Per instance.
pausedbooleanfalseFreeze this instance on its last painted frame. Per instance.
borderRadiusnumberauto detectedCorner radius in CSS px. Read from the child on every resize when omitted, and clamped to half the shorter side.
normalizeHostStylesbooleantrueStrip the child's border, outline, background and box-shadow so they do not compete with the ring.
reflectionTargetsRef<HTMLElement | null>[]Neighbour elements that receive a soft proximity reflection. Dark theme only. Pass a stable array so it is not re-registered on every render.
disableGlowbooleanfalseHide the wandering halo. The ring still renders.
shaderScalenumberper variantOverride the sampling scale. Higher zooms into the pattern.
ringCssPxnumberper variantOverride the ring thickness in CSS px.
scalenumber1Master multiplier for every absolute-pixel constant, for when the element renders at a non-1x size.
classstringMerged onto the wrapper element.
SlotDescription
defaultThe single element to wrap. Its measured box drives the effect, and its border radius is read when borderRadius is omitted.

Accessibility

Keyboard shortcuts and ARIA behavior.

  • The canvas, inner spacer and glow layers are decorative, marked aria-hidden and non-interactive, so focus and screen readers reach the wrapped control unchanged.
  • Pointer events pass through the effect layers to the child, so the wrapped button stays clickable and keyboard focusable.
  • Each instance stops compositing when scrolled offscreen (IntersectionObserver with a 64px margin) and resumes on the way back in.
  • One shared WebGL canvas serves every instance on the page, and the wrapper stays hidden until its first frame is painted, so there is no flash of an unsized canvas.