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

@sohumsuthar/liquid-glass

v1.0.0

Published

Physically accurate Liquid Glass design system — 4-layer architecture, physics-based SVG refraction, performance gates, control dock.

Readme

Liquid Glass

A physically accurate glass material for the web.

4-layer compositing architecture, physics-based SVG refraction using Snell's law and the convex squircle surface profile, performance-gated to run 20+ concurrent glass elements at 120fps.

Blog post: the physics behind my site's new ui


Quick Start

npm install @sohumsuthar/liquid-glass
// Import the core CSS (required)
import '@sohumsuthar/liquid-glass/css/liquid-glass-core.css'

// Optional CSS modules
import '@sohumsuthar/liquid-glass/css/liquid-glass-nav.css'
import '@sohumsuthar/liquid-glass/css/liquid-glass-effects.css'
import '@sohumsuthar/liquid-glass/css/liquid-glass-dock.css'
import '@sohumsuthar/liquid-glass/css/liquid-glass-utils.css'

// React component
import { LiquidGlass } from '@sohumsuthar/liquid-glass'

<LiquidGlass macro contentStyle={{ padding: '24px' }}>
  <h2>Hello</h2>
</LiquidGlass>

Or use the HTML classes directly:

<div class="liquid-glass">
  <div class="liquid-glass-effect"></div>
  <div class="liquid-glass-tint"></div>
  <div class="liquid-glass-shine"></div>
  <div class="liquid-glass-content" style="padding: 24px;">
    Your content here.
  </div>
</div>

Architecture

Every glass surface is four absolutely-positioned layers inside a container, plus two pseudo-elements on the container itself:

┌─ .liquid-glass ──────────────────────────────────────┐
│  ::before (z:2)  → static noise grain, soft-light    │
│                                                       │
│  ┌─ .liquid-glass-effect (z:0) ─────────────────┐    │
│  │  backdrop-filter: blur saturate brightness    │    │
│  │  + url(#lg-refract) SVG displacement on all    │    │
│  └───────────────────────────────────────────────┘    │
│  ┌─ .liquid-glass-tint (z:1) ───────────────────┐    │
│  │  solid base color - visible even on black     │    │
│  └───────────────────────────────────────────────┘    │
│  ┌─ .liquid-glass-shine (z:2) ──────────────────┐    │
│  │  4 inset box-shadows (lit bezel)              │    │
│  └───────────────────────────────────────────────┘    │
│  ┌─ .liquid-glass-content (z:3) ────────────────┐    │
│  │  your content                                 │    │
│  └───────────────────────────────────────────────┘    │
│                                                       │
│  ::after (z:4)   → cursor-tracking glow, screen      │
└───────────────────────────────────────────────────────┘

Why four layers?

A single backdrop-filter element can't simultaneously:

  • Blur the background (layer 0)
  • Provide a visible base tint that shows on pure-black backgrounds (layer 1)
  • Render sub-pixel inset rim highlights that track border-radius (layer 2)
  • Keep content above all material effects (layer 3)

The split architecture lets each layer use a different z-index, blend mode, and composition strategy independently.

The lit bezel

Apple's glass has a characteristic bright edge just inside the rounded border. This is four stacked inset box-shadows:

box-shadow:
  inset 0 0 0 1px rgba(255, 255, 255, 0.06),     /* 1px ring */
  inset 0 0 6px 0 rgba(255, 255, 255, 0.04),     /* feathered glow */
  inset 0 2px 4px -2px rgba(255, 255, 255, 0.18), /* top specular */
  inset 0 -2px 4px -2px rgba(0, 0, 0, 0.25);     /* bottom shadow */

The top-to-bottom opacity ratio is $0.18 : 0.25 \approx 1 : 1.4$. This asymmetry is what makes the glass read as convex rather than flat. Invert the ratio for concave; equal values for flat.

The base tint

The #1 mistake in every glassmorphism tutorial: using background: rgba(255,255,255,0.1) with mix-blend-mode: overlay. This vanishes on pure-black backgrounds.

Apple's Control Center panels have a visible dark-gray fill even on black. The tint layer uses a solid color with opacity:

| Mode | Inner cards | Macro wrappers | |------|-------------|----------------| | Dark | rgba(28, 28, 32, 0.30) | rgba(20, 20, 24, 0.30) | | Light | rgba(255, 255, 255, 0.40) | rgba(255, 255, 255, 0.40) |

No blend mode. Just a partially-transparent fill.


Physics of Refraction

Standard glassmorphism uses backdrop-filter: blur() which simulates frosted glass - light scattering uniformly. Real glass doesn't just scatter; it bends light based on surface curvature. This bending is refraction.

Snell's Law

When light passes from a medium with refractive index $n_1$ into a medium with index $n_2$, the relationship between the incident angle $\theta_1$ and the refracted angle $\theta_2$ is:

$$n_1 \sin(\theta_1) = n_2 \sin(\theta_2)$$

For air ($n_1 = 1.0$) into glass ($n_2 = 1.5$):

$$\sin(\theta_2) = \frac{\sin(\theta_1)}{1.5}$$

The refracted ray bends toward the surface normal when entering a denser medium, then bends away when exiting. For thin glass viewed head-on, this produces a lateral displacement of the background - content behind the glass edge appears shifted inward.

Surface Function

The amount of bending depends on the slope of the glass surface at each point. We define the glass cross-section as a height function $f(x)$ where $x \in [0, 1]$ is the normalized distance from the outer border ($x = 0$) to the inner flat surface ($x = 1$).

The surface uses the convex squircle (superellipse) profile:

$$f(x) = \sqrt[4]{1 - (1-x)^4}$$

This is the fourth root of a quartic complement. Compare with a simple circular arc:

$$f_{\text{circle}}(x) = \sqrt{1 - (1-x)^2}$$

The squircle's advantage: it has a softer transition from flat interior to curved bezel. The circular arc creates a harsh inflection point where the curve meets the flat zone, producing visible refraction artifacts when stretched into rectangles. The squircle keeps gradients smooth.

Computing Displacement

At each point in the bezel zone, the surface slope determines how much a light ray is displaced:

$$\text{slope}(x) = f'(x) = \frac{d}{dx} \sqrt[4]{1 - (1-x)^4}$$

Numerically approximated:

$$f'(x) \approx \frac{f(x + \delta) - f(x - \delta)}{2\delta}, \quad \delta = 0.001$$

The displacement magnitude is derived from a simplified single-refraction model:

$$d(x) = \frac{f'(x)}{1 + f'(x)^2}$$

This has the desired property: maximum at the border (where slope is steepest), decaying to zero at the flat interior.

We precompute 127 displacement samples along one radius (matching the 8-bit channel resolution of the displacement map):

for (let i = 0; i <= 127; i++) {
  const x = i / 127
  const slope = fPrime(x)
  dispLUT[i] = slope / (1 + slope * slope)
}

Vector Field

The displacement map needs both magnitude and direction at every pixel. Direction is determined by the gradient of the signed distance field (SDF) of the rounded rectangle:

$$\vec{n}(p) = \nabla , \text{SDF}(p)$$

This gradient always points orthogonal to the nearest border, exactly the direction a refracted ray would shift. For convex glass, the displacement is inward (toward the center):

$$\vec{d}(p) = -\vec{n}(p) \cdot \frac{d(\lVert p - \text{border} \rVert / w)}{d_{\max}}$$

where $w$ is the bezel width and $d_{\max}$ normalizes the output to $[-1, 1]$.

Encoding as RGB

SVG's <feDisplacementMap> reads displacement from an image. Each pixel's red channel encodes the X displacement, green encodes Y. The neutral value (no displacement) is 128:

$$R = 128 + d_x \cdot 127$$ $$G = 128 + d_y \cdot 127$$ $$B = 128, \quad A = 255$$

The scale attribute on <feDisplacementMap> multiplies the decoded displacement:

  • A pixel with $R = 255$ displaces by $+\text{scale}$ pixels in X
  • $R = 0$ displaces by $-\text{scale}$ pixels
  • $R = 128$ is neutral

With filterUnits="objectBoundingBox" and primitiveUnits="objectBoundingBox", scale is a fraction of the element's dimensions. A scale of 0.45 produces a maximum displacement of 45% of the element width. Both #lg-refract (macros) and #lg-refract-sm (inner cards) use the same 0.45 scale.

Generating the Map

npm run generate-displacement-map

This runs scripts/generate-displacement-map.mjs which:

  1. Creates a 512×512 canvas
  2. For each pixel, computes the SDF distance to a rounded rectangle (radius 48px, bezel 48px)
  3. If inside the bezel zone, looks up the precomputed displacement magnitude
  4. Computes the SDF gradient for direction
  5. Encodes as R/G pixel values
  6. Writes the PNG

The output is a color-encoded vector field where the bezel ring shows up as colored bands (red/green/cyan/magenta gradients) and the flat interior is neutral gray (128, 128).

Applying via SVG

The displacement PNG must be base64-inlined in the SVG. feImage silently fails to load external URLs from zero-size SVGs in some browsers:

<LiquidGlassFilter displacementMap={base64DataUrl} />

The CSS chains it with backdrop-filter:

backdrop-filter: blur(2px) saturate(180%) brightness(1.06) contrast(1.04) url(#lg-refract);

Chrome-only. Safari and Firefox ignore url() in backdrop-filter and fall back to the blur-only -webkit-backdrop-filter. Graceful degradation, not feature parity.


Performance

Running 20+ backdrop-filter elements over an animated particle canvas at 120fps requires aggressive gating:

| Gate | Effect | Savings | |------|--------|---------| | content-visibility: auto | Off-screen glass skips rendering | ~80% with 20+ cards | | contain: layout paint | Isolates repaint scope per card | ~20% paint reduction | | isolation: isolate | Reduces backdrop sample region | 30-40% filter cost | | SVG displacement on all surfaces | Unified config, #lg-refract macros + #lg-refract-sm inner cards | Full refraction everywhere | | Blur capped at 2px | Clear glass - minimal blur cost | ~90% vs 28px | | Particle canvas at 30fps | Halves backdrop-filter cache invalidation | ~50% composite cost | | Touch gates (hover: none) | Disables spotlight, cursor, reveal, squash | 100% on mobile |

Touch Device Behavior

On (hover: none) / (pointer: coarse) devices:

  • Cursor spotlight → hidden
  • Scroll reveal → elements visible immediately, no animation
  • Per-glass cursor glow → hidden
  • Scroll-velocity squash → disabled
  • Particle canvas → hidden
  • content-visibility → reverted to visible
  • Matching 2px blur

API

CSS Classes

| Class | Purpose | |-------|---------| | .liquid-glass | Container - applies all 4 layers when children are present | | .lg-macro | Uses #lg-refract (larger-scale displacement); inner cards use #lg-refract-sm | | .lg-mobile-flat | Strips the container on screens ≤639px | | .lg-navbar | Dynamic Island pill with sticky positioning | | .lg-nav-btn | Circular icon button | | .lg-nav-link | Text pill nav link (+ .is-active) | | .lg-ai-gradient | Animated rainbow border (@property --lg-ai-angle) | | .lg-tag | Small glass pill (tags, badges) | | .lg-search-input | Spotlight-style input | | .lg-spotlight-dropdown | Glass dropdown panel | | .lg-mono | SF Mono / JetBrains Mono monospace | | .lg-cursor-blink | Terminal cursor blink animation | | .lg-logo-spin | Loading spinner rotation (2.4s) |

HTML Class Toggles

| Class on <html> | Set by | Effect | |---|---|---| | dark | Theme toggle | Switches all glass to dark mode values | | glass-off | GlassToggle | Strips all .liquid-glass containers | | particles-off | ParticleBackground | Marks particle canvas disabled | | over-glass | Cursor hook | Brightens sitewide spotlight | | scrolled | Scroll hook | Shrinks sticky navbar |

React Components

| Component | Props | Description | |-----------|-------|-------------| | <LiquidGlass> | macro, mobileFlat, className, contentClassName | 4-layer container | | <LiquidGlassFilter> | displacementMap (base64 data URL) | Inline SVG refraction filter | | <ParticleBackground> | - | Canvas particle network + toggle button | | <GlassToggle> | - | Strips glass containers via html.glass-off | | <FirstVisitTooltip> | - | One-time "adjust UI here" hint | | <KeyboardHelpOverlay> | - | Press ? for man-page shortcuts |

Hook

import { useLiquidGlassEffects, Spotlight } from '@sohumsuthar/liquid-glass/hooks'

useLiquidGlassEffects({
  cursor: true,     // --mx/--my on closest glass
  spotlight: true,   // --cx/--cy on :root
  reveal: true,      // IntersectionObserver entrance
  scroll: true,      // html.scrolled + --sv/--svmag
  routeKey: '/',     // re-scan on route change
})

Measured Values

From Apple.com DOM inspection and WWDC 2025 reverse engineering:

| Property | All surfaces (unified) | |----------|------------------------| | Blur radius | 2px | | Saturate | 180% | | Brightness | 1.06 | | Contrast | 1.04 | | SVG displacement | scale 0.45 (#lg-refract macros, #lg-refract-sm inner cards) | | Dark tint | rgba(28,28,32,0.30) | | Light tint | rgba(255,255,255,0.40) | | Background opacity | 0.12-0.15 (nearly transparent) | | Border-radius | 22px |

Apple motion curves:

| Name | Value | Use | |------|-------|-----| | --ease-apple-out | cubic-bezier(0.32, 0.72, 0, 1) | Standard transitions | | --ease-apple-morph | cubic-bezier(0.4, 0, 0.2, 1) | Dynamic Island morph | | --ease-apple-liquid | cubic-bezier(0.3, 0, 0, 1.3) | Overshoot entrance | | --ease-apple-magnetic | cubic-bezier(0.34, 1.56, 0.64, 1) | Spring snap |


Files

css/
  liquid-glass-core.css     4-layer glass, glass-off mode, touch gates
  liquid-glass-nav.css      navbar, buttons, links, tags, search
  liquid-glass-effects.css  cursor spotlight, scroll reveal, velocity squash
  liquid-glass-dock.css     particle/glass/theme toggle pills
  liquid-glass-utils.css    monospace, cursor blink, typography

components/
  LiquidGlass.jsx           <LiquidGlass macro> wrapper
  LiquidGlassFilter.jsx     SVG displacement filter (inline)
  ParticleBackground.jsx    canvas network + 30fps throttle + mouse interaction
  GlassToggle.jsx           toggle strips glass containers
  FirstVisitTooltip.jsx     one-time dock hint
  KeyboardHelpOverlay.jsx   press ? for man-page shortcuts

hooks/
  useLiquidGlassEffects.js  4-in-1: cursor, spotlight, reveal, scroll

scripts/
  generate-displacement-map.mjs   physics-based refraction PNG generator

SKILL.md                    LLM-readable skill for Claude Code / Cursor

License

MIT