npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vepando/liquid-glass-react

v1.2.1

Published

Apple-style Liquid Glass for React: physically-based refraction (Snell's law), specular rim and touch-reactive glass via SVG backdrop-filter. No WebGL.

Readme


Most "liquid glass" CSS on the web is backdrop-filter: blur() in a rounded box. Real Liquid Glass bends light: the centre stays clear, while the curved rim magnifies and stretches whatever is behind it and catches a bright edge highlight.

This library models that rim as an actual lens. It traces light through it with Snell's law, writes the result into an SVG displacement map sized to each element, and lets the browser apply it in its normal backdrop-filter pass.

npm install @vepando/liquid-glass-react
import "@vepando/liquid-glass-react/style.css"
import { LiquidGlass } from "@vepando/liquid-glass-react"

<LiquidGlass radius={999} interactive style={{ height: 64 }}>
  <nav>…</nav>
</LiquidGlass>

Contents

Features

  • Physically based refraction. The rim is a convex squircle bezel. Rays are refracted at n = 1.5 and traced down to the content beneath. The centre stays undistorted and the rim magnifies.
  • Pixel-exact maps per element. Each map is generated for the element's measured size and regenerated on resize, so the bend sits right on the curve whatever the shape.
  • Clear rim, tinted centre. The tint fades out across the bezel, so the refracted backdrop shows bright at the edge, as in Apple's glass.
  • Angle-dependent specular. A crisp edge line plus a soft inner glow that follows a configurable light direction.
  • Chromatic dispersion. R, G and B bend by slightly different amounts for a faint colour fringe on high-contrast edges.
  • Touch interaction. interactive adds a springy press-to-grow and a glow from the touch point.
  • Light and dark tints, or any CSS colour.
  • SSR safe. A frosted fallback renders on the server and upgrades on mount, without hydration mismatches.
  • Cached maps. Identical controls share maps. New sizes and optical settings generate maps synchronously; large panels and continuous resizing can be expensive. SVG backdrop rendering still costs work every frame, especially with dispersion. Many-element rendering has not been benchmarked across browsers and devices.
  • Zero dependencies. React 18+ is the only peer dependency. ESM + CJS + TypeScript types, about 5 KB gzipped.

Install

npm install @vepando/liquid-glass-react
# or: pnpm add @vepando/liquid-glass-react · yarn add @vepando/liquid-glass-react · bun add @vepando/liquid-glass-react

Import the stylesheet once, e.g. in your root layout or entry file:

import "@vepando/liquid-glass-react/style.css"

Then use the component anywhere:

import { LiquidGlass } from "@vepando/liquid-glass-react"

Next.js App Router: the bundle is already marked "use client", so you can render <LiquidGlass> straight from a server component. Put the CSS import in app/layout.tsx. Tested with next build and React 18/19 SSR.

Prefer to own the code? The source is in src/. The copy-paste version in skills/liquid-glass/assets/ works without the package.

Usage

Floating navbar

<LiquidGlass radius={999} className="h-16 w-full max-w-3xl">
  <nav className="h-full flex items-center justify-between px-6">…</nav>
</LiquidGlass>

Round icon button

<LiquidGlass radius={999} interactive className="h-16 w-16">
  <button type="button" aria-label="Search" onClick={openSearch}
    className="h-full w-full rounded-full border-0 bg-transparent cursor-pointer focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2">
    <SearchIcon aria-hidden="true" />
  </button>
</LiquidGlass>

interactive adds a pointer animation to a div; it does not turn it into a button. Put a native button or link inside for keyboard access, activation and focus. The utility classes in these examples assume Tailwind; equivalent CSS works too.

Dark sheet / panel

<LiquidGlass radius={56} tint="dark" className="p-12">
  <p className="text-white text-4xl">It'll be fantastic weather…</p>
</LiquidGlass>

Glass on glass (e.g. a tab bar selection droplet). Render the inner glass as a sibling positioned over the outer one, not as a child (see Gotchas):

<div className="relative">
  <LiquidGlass radius={999} className="h-16">{tabs}</LiquidGlass>
  <div className="absolute inset-1 pointer-events-none">
    <LiquidGlass radius={999} blur={0} tint="rgba(255,255,255,.05)"
      style={{ width: "25%", height: "100%", transform: `translateX(${active * 100}%)` }} />
  </div>
</div>

A full working version, including the stretch while it slides, is in demo/src/Scenes.tsx.

UI components

Ready-made, themeable components for real apps: Button, Tabs, Dialog and Card. They are built on Base UI for keyboard and screen reader behaviour, and installed as source code through the shadcn CLI, so they live in your project and you (or your coding agent) can change them.

npx shadcn@latest add https://leonardseo.github.io/liquid-glass-react/r/glass-button.json
npx shadcn@latest add https://leonardseo.github.io/liquid-glass-react/r/glass-tabs.json
npx shadcn@latest add https://leonardseo.github.io/liquid-glass-react/r/glass-dialog.json
npx shadcn@latest add https://leonardseo.github.io/liquid-glass-react/r/glass-card.json

This needs a project set up for shadcn with Tailwind CSS v4. The tokens come along in components/ui/glass-tokens.css: change the variables there to theme everything, including dark mode through a .dark class or data-theme="dark" on <html>.

import { GlassButton } from "@/components/ui/glass-button"

<GlassButton variant="primary">Save</GlassButton>
<GlassButton loading>Saving</GlassButton>

GlassTabBar is an iOS-style tab bar or navigation: a quiet pill at rest, and a magnifying lens you can drag between items. Give items an href for a navigation; without one they become tabs with arrow-key support.

<GlassTabBar aria-label="Sections" value={tab} onValueChange={setTab} items={items} />

Materials follow Apple's glass styles:

| Material | Used for | Character | | --- | --- | --- | | regular | Default for every component | Frosted and bright, for controls and surfaces with text | | clear | Glass over rich media | See-through, with a refracting rim. Check that text on it stays legible |

One layer of glass per stack. A backdrop-filter hides the page from everything inside it, so glass nested in glass has nothing to refract. GlassCard and GlassDialogContent handle this for you: a GlassButton inside them switches to inset glass, which keeps the rim and tint but has no backdrop of its own. For the same reason the track of GlassTabsList is not glass, so the indicator moving over it can refract.

Performance. Maps are built once per distinct size and shared, so a page of uniform controls is cheap. Fifty different sizes at once take noticeably longer on slow devices (about 225 ms under 4× CPU throttling, in one block on first render).

Props

Set the size with CSS and choose from the same options Apple's glass offers:

| Prop | Default | Description | | --- | --- | --- | | variant | "regular" | Glass style: "regular" (frosted, matched against macOS's NSGlassEffectView), "clear" (see-through, bends what is behind it) or "identity" (no glass). Add a dark class or data-theme="dark" on the glass or an ancestor for dark mode. | | radius | 32 | Corner radius in px. 999 for pills and circles. "auto" reads the CSS border-radius, so rem and tokens work. | | tint | none | Any CSS colour, ideally translucent. | | interactive | false | Press-to-grow and touch glow, for controls. | | render | - | Render as another element, e.g. render={<button type="button" />}. |

Every other div prop (className, style, onClick, role, …) is passed through, and ref reaches the element.

Building your own components

useLiquidGlass gives you the engine without the div, for use with Base UI, Radix or your own markup. It takes the same options as the props above, with radius defaulting to "auto".

import { Button } from "@base-ui/react/button"
import { useLiquidGlass } from "@vepando/liquid-glass-react"

function GlassButton({ children }) {
  const glass = useLiquidGlass()
  return (
    <Button ref={glass.ref} {...glass.rootProps} className={`${glass.rootProps.className} rounded-full`}>
      {glass.wrap(children)}
    </Button>
  )
}

ref must reach the DOM element, and wrap must be its only child. If you pass your own className, style or pointer handlers, merge them with rootProps (Base UI's mergeProps does this).

Responsive layout and geometry

Width and height can use rem, %, vw, grid or flex layouts. The component measures the border box in CSS pixels and regenerates its maps when that box changes, including padding and borders. CSS pixels are the coordinate system of the generated filter, not physical device pixels.

<LiquidGlass radius={999} style={{ width: "100%", maxWidth: "40rem", height: "4rem" }}>
  <nav>…</nav>
</LiquidGlass>

How it works

flowchart LR
  A[ResizeObserver<br/>element size] --> B[Snell's law over<br/>squircle bezel]
  B --> C[Displacement map<br/>R = x, G = y]
  A --> D[Specular rim map]
  A --> E[Tint mask]
  C --> F["SVG filter<br/>feImage → feDisplacementMap ×3"]
  F --> G["backdrop-filter:<br/>blur() url(#filter) saturate()"]
  D --> H[plus-lighter overlay]
  E --> I[masked tint layer]
  1. Optics. For each distance across the bezel, the surface slope of the squircle profile gives a normal. A straight-down view ray is refracted into the glass and followed to the content plane. The sideways offset is the displacement: 0 on the flat plateau, large near the edge.
  2. Map. That 1-D curve is swept around the rounded-rect outline along the inward normal and encoded into R/G, where 128 means no shift. feDisplacementMap then samples the backdrop from further inside the glass near the rim, which reads as magnification.
  3. Compositing. The filter is referenced from backdrop-filter on the component root. A masked tint, the specular map (mix-blend-mode: plus-lighter) and your content sit on top.

The deep dive is in skills/liquid-glass/references/theory.md.

Browser support

| Engine | Result | | --- | --- | | Blink: Chrome, Edge, Arc, Brave, Opera | Full effect: refraction, dispersion, specular, frost | | WebKit: Safari and every iOS browser | Frosted fallback: blur, tint and specular rim, no refraction | | Gecko: Firefox | Same frosted fallback |

The stylesheet also responds to prefers-reduced-transparency (solid surface), prefers-contrast: more (near-opaque surface with a visible edge), forced-colors and prefers-reduced-motion.

Only Blink renders SVG filters referenced from backdrop-filter. The other engines accept the syntax but draw nothing, so a CSS cascade fallback isn't reliable there. The component detects Blink and only adds url(#filter) there.

Gotchas

No refraction at all, the page just shows through the glass? You have a backdrop root in the way.

  • An ancestor with filter, opacity < 1, mask, clip-path, mix-blend-mode, backdrop-filter, or children that blend into it, cuts the backdrop off at that ancestor. The glass then refracts an empty image. That's why backdrop-filter sits on the component root, never on an inner layer. transform and overflow: hidden are fine.
  • Glass inside glass can't see the outer glass's refraction. Use siblings positioned on top (see Usage).
  • Don't declare backdrop-filter on .lg in your own CSS. Minifiers such as Lightning CSS (the Vite 8 default) can collapse a backdrop-filter and -webkit-backdrop-filter pair with equal values to the prefixed one alone, which Chromium ignores. To replace the filter, set --lg-backdrop-override instead.
  • Give the glass something to bend. Over a flat colour, any glass looks like a tinted rectangle. Refraction shows over images, text and edges.

Static / zero-JS variant

For fixed-size elements, the same optics are available as a Python script that writes a PNG and prints the matching scale:

pip install -r skills/liquid-glass/scripts/requirements.txt
python3 skills/liquid-glass/scripts/generate-displacement-map.py --width 700 --height 64 --radius 32 \
  --output public/liquid-lens-map.png
# → Use it with: <feDisplacementMap scale="24.0" xChannelSelector="R" yChannelSelector="G" />

Generate the map at the element's exact CSS size; a stretched map puts the bend in the wrong place.

Demo

Open the live demo — no installation required.

Glass lab · Showcase · Classic demo

Chromium shows the full refraction effect; other browsers get the frosted fallback. The demo deploys to GitHub Pages after successful checks on main.

To run locally:

cd demo
npm install
npm run dev

| URL | Scene | | --- | --- | | / | Homepage with draggable glass and links to all demos | | /?page=lab | Squircle, orb and pill lenses; draggable with mouse, touch or arrow keys; live backdrop, style, tint, interactive and corner radius controls | | /?page=showcase | Interactive artwork gallery, music interface and weather panel | | /?page=classic | The original Music and Siri collection | | /?scene=music | Apple Music style tab bar with sliding droplet, mini player and search button over album art | | /?scene=siri | Dark glass panel over the macOS Tahoe wallpaper | | /?scene=playground | Draggable lenses over text, stripes and a grid | | /?scene=banner | The header image of this README | | /?scene=original | The v1 technique, for comparison |

The demo imports @vepando/liquid-glass-react by name; Vite aliases that name to src/, so you get HMR on the library source without a build step.

Before and after

For AI coding agents

skills/liquid-glass/ is a self-contained skill (SKILL.md, assets and theory) that you can hand to Claude Code or any other coding agent to implement the effect end-to-end, including the troubleshooting table for the common failure modes.

Repository layout

src/                  the package: LiquidGlass.tsx · useLiquidGlass.tsx · maps.ts · liquid-glass.css · index.ts
registry/glass/       source of the UI components; registry.json lists them for the shadcn CLI
dist/                 build output (npm run build), published to npm
demo/                 Vite + React showcase
scripts/              sync-skill.mjs (regenerates the skill's copy of src/)
skills/liquid-glass/  skill for AI coding agents
docs/                 images used in this README

Credits

The refraction model follows the approach described by kube.io in Liquid Glass in the browser: refraction with CSS and SVG. Liquid Glass is a design language by Apple; this project is not affiliated with Apple.

The macOS Tahoe wallpapers in demo/public/img/ and in the images in docs/ are Apple's. They are only used for the demo and are not covered by this project's MIT license or included in the npm package.

Development

npm install
npm run build      # tsup → dist/ (ESM, CJS, .d.ts, style.css)
npm run typecheck
npm test           # geometry and component tests; Node 22.18+ or 24+
npm run registry:build  # shadcn registry → demo/public/r
cd demo && npm install && npm run dev

The components are at ?scene=ui in the demo, and ?scene=bench&mode=dense (also same, varied, panel, move, resize, scroll, nest) is the measuring page.

Publishing: npm publish (runs typecheck and build first via prepublishOnly).

License

MIT