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

@jole_0/liquidglass-web

v0.1.0

Published

A glassmorphic React component library with a real WebGL refraction tier.

Readme

Liquid Glass for Web

License Stars Last commit JS CSS

A glassmorphic React component library built on one portable CSS material, with an opt-in WebGL refraction tier for hero elements.

Run pnpm dev for the documentation site — an interactive breakdown of the material, a live token playground, and every component with its source.

Why another glass library

Most glassmorphism is backdrop-filter: blur() and a 1px white border, which is why most of it looks like frosted plastic. This library treats the material as the hard part:

  • Saturation in the backdrop filter, not just blur, so colour behind the panel blooms through it. This is the single biggest tell between real and fake.
  • An asymmetric rim light — a gradient border brighter on the lit edge, rather than a uniform stroke. A flat 1px rgba border reads as a sticker.
  • Inset specular and inner shadow, so the panel reads as having thickness.
  • SVG noise microtexture at 3.5%. Removing the perfectly smooth gradient is what kills the plastic look.
  • Layered shadows — a tight contact shadow that anchors the element plus a wide ambient one, rather than a single soft blur.
  • One light direction for the whole page, enforced by a token rather than by per-component values.
  • A chromatic edge fringe faking dispersion at the rim.
  • Adaptive contrast — sample the backdrop's luminance and flip the theme so glass stays legible over arbitrary imagery.

Install

npm install @jole_0/liquidglass-web
# pnpm add @jole_0/liquidglass-web
# yarn add @jole_0/liquidglass-web
# bun add @jole_0/liquidglass-web
import { Button } from '@jole_0/liquidglass-web';
import '@jole_0/liquidglass-web/styles.css';

<Button variant="primary">Continue</Button>;

The stylesheet is a separate import rather than injected by JS, so you control where it lands in your cascade.

Components

| Component | Notes | | -------------- | ---------------------------------------------------------------- | | GlassSurface | The material primitive. Everything else is built on it. | | Button | primary / secondary / ghost, three sizes, icons, loading. | | Card | Slot-based, with media that bleeds to the corners. | | Input | Recessed glass, with label, hint and error wiring. | | Switch | role="switch", controlled or uncontrolled. | | Tabs | Full ARIA tabs pattern with a measured sliding indicator. | | Navbar | Sticky, with glass that thickens as the page scrolls under it. | | Modal | Focus trap, focus restore, scroll lock, glass scrim. | | Tooltip | Hover and focus, Escape to dismiss, flips at viewport edges. | | GlassStage | Tier 2 root. See below. |

Theming

Everything resolves through custom properties. Override them on :root or on any subtree:

:root {
  --lg-light-angle: 145deg; /* moves every rim, specular and shadow */
  --lg-blur: 20px;
  --lg-saturate: 180%;
  --lg-tint-hue: 220;
  --lg-accent-hue: 220;
  --lg-noise-opacity: 0.035;
}

src/lib/styles/tokens.css is the full list, and it is commented.

For glass over a light backdrop, add .lg-theme-light — or let the hook decide:

const panelRef = useRef<HTMLDivElement>(null);
const imageRef = useRef<HTMLImageElement>(null);

useAdaptiveContrast(panelRef, { source: imageRef });

It samples the pixels of the img, canvas or video behind the panel and sets data-lg-contrast on it, which flips the whole token theme. Luminance is computed in linear light rather than as a channel average, because a naive mean rates saturated blue and saturated yellow as equally bright and puts unreadable text over the blue. There is hysteresis around the threshold, so a backdrop sitting right on the boundary holds its theme instead of flipping every frame.

The two tiers

Tier 1 is backdrop-filter glass. It is cheap, composes anywhere, scales to hundreds of elements, and every component uses it. This is the default and almost always what you want.

Tier 2 is real WebGL refraction — it bends the pixels behind the panel rather than blurring them. It needs the optional peer dependency:

pnpm add @ybouane/liquidglass
<GlassStage>
  <img src="/hero.jpg" alt="" />
  <GlassPanel radius={24} button floating>
    Refracting
  </GlassPanel>
</GlassStage>

GlassPanel must be a direct child of GlassStage. That is not a style preference: the renderer silently drops any glass element that is not a direct child of its root, so the constraint is built into the component API and checked at mount with a dev-time warning.

Things worth knowing before reaching for it:

  • Reserve it for one or two showpiece elements. Cost is per element, per frame, and scales with area times DPR squared. Nothing in the config reduces per-pixel work — the shader always takes six texture samples per pixel and always evaluates its noise, whatever chromAberration or blurAmount say. Area is the only real lever.
  • Use it over media. Anything behind a panel that is not a canvas, img or video is rasterized through html-to-image and re-captured on any subtree mutation. Over live text or lists it is impractical; over imagery it takes a fast drawImage path.
  • A <video> or [data-dynamic] anywhere inside the stage forces every glass panel to re-render every frame.
  • Moving a panel yourself does not trigger a re-render. The frame loop early-outs unless something is explicitly dirty. Move a panel with your own transform and the stale texture rides along with it; call markChanged from useGlassStage() after you do.

If the package is missing, WebGL is unavailable, or a shader fails to compile, panels stay on the Tier 1 material rather than becoming transparent holes. The renderer is loaded with a dynamic import and marked external in the build, so none of its ~100 kB reaches consumers who never use it.

Accessibility

  • One focus ring across the library, layered so it is visible over both light and dark backdrops.
  • prefers-reduced-motion cuts transitions to a single frame and drops transforms. The button spinner pulses in place rather than disappearing.
  • forced-colors abandons the material entirely for system colours and a plain border, and swaps the shadow-based focus ring for an outline — shadows are not rendered in that mode, so the indicator would otherwise vanish.
  • prefers-contrast: more thickens the tint and rim.
  • @supports not (backdrop-filter) falls back to an opaque-enough tint rather than leaving text over an unblurred backdrop.

Development

pnpm install
pnpm dev        # documentation site
pnpm build      # documentation site -> dist-site
pnpm build:lib  # library -> dist (ESM + CJS + types + one stylesheet)
pnpm typecheck

The site in src/site consumes src/lib directly — its navbar, tabs, switches and cards are the real components, so the documentation cannot drift from what it documents.

Licence

MIT