React Liquid Glass Component for Interactive UI Effects

Description:

Liquid Glass is a headless React component that applies realistic glassmorphism effects to live DOM elements.

An SVG displacement filter bends the page behind the glass, keeping text selectable and links clickable in Chrome, Safari, and Firefox with zero runtime dependencies.

You can use it for glass buttons, frosted cards, notification panels that refract a background image, or video player controls where every button is its own lens bending the footage.

Preview

React Liquid Glass Component for Interactive UI Effects

Features

  • Renders live, interactive glass that bends the real DOM with an SVG displacement map.
  • Works in Chrome, Safari, and Firefox, No fallback to a flat blur for the default frost.
  • Multiple refraction modes: live DOM, a copy of any node, and WebGL for video/canvas.
  • Ships with a headless, composable API. Wrap any styled box and it turns into glass.
  • Provides optics controls for strength, depth, curvature, dispersion, bend, sheen, glow, and frost.
  • Includes motion utilities for pointer‑driven lenses, velocity squash‑stretch, and framer‑motion‑compatible values.

How To Use It

Installation

Install the package with npm:

npm install @samasante/liquid-glass

react and react-dom are peer dependencies. The package does not require a CSS import, provider, or root-level setup. In Next.js App Router projects, place Glass inside a Client Component because it measures layout and uses browser APIs.

Basic Usage

A bare Glass wrapper creates a frosted, tinted material. Its children stay sharp above the glass layer.

"use client";
import { Glass } from "@samasante/liquid-glass";
export function BillingButton() {
  return (
    <Glass
      className="inline-flex"
      style={{
        background: "rgba(15, 23, 42, 0.42)",
        borderRadius: 14,
        padding: "12px 18px",
      }}
      optics={{ frost: 2, dispersion: 0.28 }}
    >
      <button type="button" className="font-medium text-white">
        Update plan
      </button>
    </Glass>
  );
}

This material mode frosts, tints, and highlights the wrapper in Chrome, Edge, Safari, and Firefox. Refraction of arbitrary content behind a fixed panel relies on Chromium support for backdrop-filter: url(). Use an explicit lens or a refract copy when Safari and Firefox need visible refraction too.

More Examples

Move a lens across wrapped DOM content

Pass size and center when the lens should bend content inside its own wrapper. glassValue() avoids React re-renders for pointer movement.

"use client";
import { useMemo } from "react";
import { Glass, glassValue } from "@samasante/liquid-glass";
export function FeatureLens() {
  const lensX = useMemo(() => glassValue(0.5), []);
  const lensY = useMemo(() => glassValue(0.5), []);
  return (
    <Glass
      size={180}
      radius={90}
      center={{ x: lensX, y: lensY }}
      className="relative block overflow-hidden rounded-3xl"
      optics={{
        strength: 0.1,
        depth: 0.9,
        curvature: 0.34,
        dispersion: 0.3,
        frost: 0.7,
      }}
      onPointerMove={(event) => {
        const bounds = event.currentTarget.getBoundingClientRect();
        lensX.set((event.clientX - bounds.left) / bounds.width);
        lensY.set((event.clientY - bounds.top) / bounds.height);
      }}
    >
      <section className="min-h-[320px] bg-slate-950 p-10 text-white">
        <p className="text-sm text-slate-300">Workspace analytics</p>
        <h2 className="mt-4 max-w-md text-4xl font-semibold">
          Review product activity from one screen.
        </h2>
      </section>
    </Glass>
  );
}

center.x and center.y accept numbers or compatible motion values. A supported motion object exposes get() and on("change", callback), which includes the package’s own signals and Framer Motion values.

Build a refracted notification over a photo

Use refract for a floating element that needs to bend a copy of content behind it. The children remain the crisp foreground layer.

import { Glass } from "@samasante/liquid-glass";
const cityPhoto = (
  <img
    src="/images/city-at-night.jpg"
    alt=""
    aria-hidden="true"
    className="h-full w-full object-cover"
  />
);
export function PhotoNotification() {
  return (
    <section className="relative h-80 overflow-hidden rounded-3xl">
      {cityPhoto}
      <Glass
        refract={cityPhoto}
        behind="#111827"
        width={380}
        height={124}
        radius={24}
        className="absolute bottom-6 left-6 p-5 text-white"
        optics={{
          strength: 0.075,
          depth: 0.82,
          curvature: 0.22,
          dispersion: 0.28,
          frost: 0.5,
        }}
      >
        <p className="text-sm text-slate-200">Build finished</p>
        <h2 className="mt-1 text-lg font-semibold">
          Preview deployment is ready.
        </h2>
      </Glass>
    </section>
  );
}

Set behind when the refracted copy sits over photography, gradients, or another background with no predictable solid edge color. It fills the small sample area outside the copied node and prevents warped edge artifacts.

Add lenses to a video control layer

Use src for video and lenses for multiple glass controls over one media surface. The component exposes the underlying <video> through videoRef.

"use client";
import { useRef, useState } from "react";
import {
  Glass,
  type GlassSurfaceLens,
} from "@samasante/liquid-glass";
const transportLenses: GlassSurfaceLens[] = [
  { x: 0.3, y: 0.5, w: 52, h: 52, radius: 26 },
  { x: 0.5, y: 0.5, w: 84, h: 84, radius: 42 },
  { x: 0.7, y: 0.5, w: 52, h: 52, radius: 26 },
];
export function ProductVideo() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [paused, setPaused] = useState(true);
  return (
    <Glass
      src="/media/product-tour.mp4"
      poster="/media/product-tour-poster.jpg"
      videoRef={videoRef}
      paused={paused}
      muted
      loop
      lenses={transportLenses}
      className="relative block aspect-video overflow-hidden rounded-3xl"
      optics={{
        depth: 0.9,
        curvature: 0.4,
        dispersion: 0.24,
        frost: 0.35,
      }}
    >
      <button
        type="button"
        className="absolute left-1/2 top-1/2 h-[84px] w-[84px] -translate-x-1/2 -translate-y-1/2 rounded-full text-sm font-semibold text-white"
        onClick={() => setPaused((current) => !current)}
      >
        {paused ? "Play" : "Pause"}
      </button>
    </Glass>
  );
}

Use crossOrigin="anonymous" or crossOrigin="use-credentials" for remote video sources that need to enter the WebGL texture pipeline.

Props and API Reference

<Glass> props

PropTypePurpose
childrenReactNodeCrisp interface layer above the refracted surface.
refractReactNodeContent copied into the refraction layer.
behindstringSolid color used beyond the edge of a copied refraction target.
width, heightNumber or motion valueLens dimensions in pixels.
sizeNumber or [width, height]Shorthand for lens dimensions.
radiusNumber or motion valueCorner radius in pixels.
center{ x, y }Lens center as a 0 to 1 fraction of the wrapper.
opticsPartial<GlassOptics>Refraction, blur, lighting, and edge behavior.
livebooleanRefreshes animated refracted DOM content frame by frame.
filterResolutionnumberChromium supersampling value. Safari forces this to 1.
srcstringVideo source for WebGL refraction.
draw(ctx, time) => voidPer-frame canvas drawing callback.
lensesGlassSurfaceLens[]Multiple lenses over a shared video or canvas surface.
videoRefReact refRef to the internally rendered video element.
pausedbooleanControlled video playback state.
poster, loop, muted, autoPlayStandard media propsVideo configuration.
crossOrigin"anonymous" or "use-credentials"Cross-origin video texture configuration.
maxDprnumberMaximum device pixel ratio for WebGL buffers.
overlayReactNodeAdditional refracted layer for advanced compositions.
onLensMapChangeCallbackReceives updated displacement-map URLs.
unstable_lensObjectLow-level lens controls that may change before version 1.0.

The component also exposes advanced fields such as depth, scale, brightnessInFilter, and pixelUnits for custom lens implementations.

optics reference

OptionPurpose
strengthSets the primary refraction amount.
scaleX, scaleYOverride refraction strength per axis.
depthControls how far refraction reaches from the edge toward the center.
curvatureAdds the convex magnification through the middle of the lens.
dispersionControls chromatic separation around refracted edges.
bendAdds the stronger liquid-style refraction near the rim.
bendWidthSets the thickness of the rim refraction band.
splayPushes the displacement field outward near corners.
frostAdds blur to the refracted source.
brightnessApplies a white or black veil over the lens.
saturateAdjusts material-mode color saturation.
specularSets the shared intensity for highlights and glow.
sheenControls directional edge highlights.
sheenWidthSets the thickness of the highlight band.
sheenFalloffAdjusts highlight falloff.
sheenAngleChanges the highlight direction in degrees.
sheenDarkUses a darker edge treatment in DOM mode.
glowControls inner glow intensity.
glowSpreadSets how far the inner glow reaches.
glowFalloffAdjusts glow falloff.
mapSizeSets the displacement-map resolution.
clipToShapeLimits optical effects to the rounded lens shape.
softEdgeAdds a softer transition toward the lens edge.
edgeShadowAdds a shadow for the active lens state.
edgeInsetShadowAdds an inset shadow for the active lens state.
restEdgeShadowAdds a shadow for the resting lens state.
restEdgeInsetShadowAdds an inset shadow for the restinglens state.

The same optics vocabulary drives both SVG-based DOM refraction and WebGL media surfaces.

Motion utilities

The package exports glassValue, animateGlassValue, deriveGlass, cubicBezier, glassEase, useLensWobble, rubberBand, and GlassDiv.

Use glassValue() for cursor-driven position changes, slider motion, or animated lens geometry. Use animateGlassValue() for timed transitions. deriveGlass() creates a value from other reactive lens values.

Alternatives and Related Resources

FAQs

Q: Does liquid-glass work in Next.js App Router projects?
A: Yes. Render it inside a Client Component with "use client". Keep static page content in Server Components and isolate interactive glass sections behind a small client boundary.

Q: Why does the glass panel look different in Safari or Firefox?
A: A bare material wrapper frosts and tints correctly across browsers, but arbitrary backdrop refraction depends on Chromium behavior. Use a geometry-based lens or pass a matching node through refract for visible cross-browser refraction.

Q: Why does the lens have a warped or dark edge over an image?
A: Set behind to a matching background color. This fills the small area sampled outside a copied refraction target.

Q: Does the package work with video?
A: Yes. Pass a video URL through src, then use videoRef, paused, and lenses for custom transport controls. Remote media needs an appropriate crossOrigin setting and server-side CORS headers.

Q: Is a CSS import required?
A: No. Style the wrapper with inline styles, CSS classes, CSS modules, or Tailwind utilities.

Add Comment