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

lensglass

v0.1.1

Published

Liquid glass for the web, with real edge refraction. An SVG displacement map traced through Snell's law bends whatever is behind each surface, plus an iOS-accurate control kit that still looks right on engines that cannot lens. No dependencies, no build s

Readme

lensglass

Liquid glass for the web, with the refraction actually done. Each surface gets an SVG displacement map traced through Snell's law, so its rim bends whatever is behind it instead of just blurring it. On top of that, an iOS-accurate control kit that still looks right on engines that cannot lens at all.

No dependencies, no build step. Two CSS files and three ES modules.

A bead of glass and a glass panel over a photograph of a panther chameleon. Inside the bead the scales stay sharp; at its rim they bend and break.

Every image in this README is a screenshot of the library running, not a mockup of it. The bead above holds a wide rim bezel, so a small core stays clear glass while everything around it bends, which is a difference you cannot get from a blur at any radius.

One disclosure, since the whole point of this page is not overselling: the screenshots run data-lg-strength between 1.35 and 1.5, above its default of 1. A still frame has to make its case in a single glance, where a live page has motion and a cursor to help it. Everything else is stock, and the default still bends light, just more quietly than this.

The honest part, first

Real edge refraction needs url() inside backdrop-filter, and only Chromium implements that. Safari and Firefox parse it and drop it, which would take the blur down with it. There is no version of this library that bends light in Safari, and anybody who tells you otherwise is blurring and calling it done.

So lensglass ships three tiers and tells you which one you got:

| tier | where | what you see | | --- | --- | --- | | refract | Chromium | rims bend the backdrop. the real thing. | | blur | Safari, Firefox | blur, vibrancy, specular rim. convincing glass, flat rims. | | solid | prefers-reduced-transparency, or no backdrop-filter at all | opaque panels that keep the shape and the depth. |

The same crop of the chameleon three times, with the same bead of glass on each. Under refract the leg and the colour bands break at the bead's rim and rejoin outside it. Under blur nothing moves. Under solid the bead is opaque.

Same crop, same bead, same blur radius. The only variable is the tier. The dashed rule is drawn over the glass rather than through it, so it stays straight in all three and gives the bands something to be measured against. If you only ever look at one picture here, look at the middle one: that is what your Safari users get, and it is what most libraries are shipping to everybody.

The tier lands on <html> as lg-tier-refract / lg-tier-blur / lg-tier-solid, so your own CSS can branch on it too. Everything in glass.css is written to stand up without the engine ever running, which means a page that loads the stylesheet and never calls init() is already correct.

Install

npm i lensglass

Or off a CDN with no bundler involved:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/glass.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/controls.css">
<script type="module">
  import { init } from 'https://cdn.jsdelivr.net/npm/[email protected]/src/index.js';
  init();
</script>

Start

<link rel="stylesheet" href="lensglass/glass.css">
<link rel="stylesheet" href="lensglass/controls.css">

<div class="lg-panel lg-spec" data-lg>
  glass
</div>
import { init } from 'lensglass';

init();   // returns 'refract' | 'blur' | 'solid'

Two things worth knowing before you wonder why it looks like nothing happened. Put something behind it. Refraction is invisible over a flat colour; it needs an image, a gradient, content, anything with structure. And data-lg is the opt-in. Adding it to every surface on the page means a GPU pass per surface, so tag the panels and the nav and leave the small stuff to .lg-lite.

A third one, which cost me an afternoon: do not let the blur get as wide as the bezel. The default blur is 30px, so a surface at data-lg-bezel="30" smears its own refraction into nothing and no amount of data-lg-strength will bring it back, because the bending is happening inside the radius the blur is averaging over. If a surface looks like frosted glass and nothing else, widen the bezel or pin a smaller data-lg-blur before you reach for strength.

Tagging a surface

<div class="lg-panel lg-spec"
     data-lg
     data-lg-bezel="24"
     data-lg-strength="0.7"
     data-lg-blur="8">

| attribute | what it does | | --- | --- | | data-lg | this is a glass surface, lens it | | data-lg-bezel | width of the lensing ring in px, or full to lens through the middle | | data-lg-strength | per-surface refraction multiplier, default 1 | | data-lg-blur | pin the blur in px instead of inheriting --lg-blur |

bezel="full" is the one to reach for on small controls. A panel is glass with a rounded edge, so it only bends at the rim; a slider thumb or a switch knob is a solid bead, and lensing it all the way through is what stops it looking like a tiny picture frame. The slider in this package does exactly that.

The engine also puts data-lg-on on any surface that is genuinely lensing, which is how the material thins its own painted border out only where refraction is already holding the edge. It is an attribute rather than a class on purpose: frameworks own className and rewrite it on re-render, and a marker that lives there gets silently wiped.

Driving it

import { init, setLevel, refresh, tier, destroy } from 'lensglass';

init({ force: 'blur' });    // pin a tier to see what Safari gets
setLevel(0.4);              // global refraction multiplier
refresh(el);                // rebuild one surface
tier();                     // what you actually got

setLevel is the cheap one. It rewrites the filter's scale and nothing else, no maps are rebuilt, so it is safe to drive straight from a slider being dragged. Building a map rasterises a canvas and encodes a PNG, which is why that only happens when a surface actually changes size.

refresh(el) matters in one specific case: a surface that was display: none when the engine found it had no measurable box, so it has no map yet. The engine notices on its own, but it is watching from outside and has to wait for a resize notification. If you are the one revealing the thing, say so. The select in this package calls refresh on its own menu for exactly this reason.

The material

Set --lg-tint and you are mostly done.

| property | default | what it does | | --- | --- | --- | | --lg-tint | #0a84ff | accent, used by fills, focus rings and tinted panes | | --lg-frost | 0.34 | base alpha of the pane | | --lg-blur | 30px | backdrop blur radius | | --lg-vibrancy | 1.8 | backdrop saturation, the vibrancy in Apple's stack | | --lg-spec | 1 | specular rim strength, 0 turns it off | | --lg-radius, --lg-r-lg, --lg-r-md, --lg-r-sm | 30/20/14/10px | the corner scale |

Dark mode follows prefers-color-scheme, and data-lg-theme="dark" or "light" on <html> overrides it in either direction so your own theme toggle wins. Ink, fills, separators and shadows all flip with it.

Classes:

  • .lg-panel the real material, with a backdrop pass
  • .lg-lite the same look faked with alpha alone, no backdrop pass. Use this by default. Every real backdrop-filter is a separate GPU pass, re-run every frame anything behind it moves; they are worth spending on large surfaces and nothing else.
  • .lg-spec the specular rim, drawn on a 1.2px ring with mask-composite, so it costs no extra element and follows whatever border-radius you already set
  • .lg-tinted accent bled through the glass
  • .lg-nav, .lg-tabbar, .lg-sheet, .lg-popover, .lg-list container shapes
  • .lg-grabber, .lg-row, .lg-row-chevron the furniture inside them

The controls

A settings sheet of glass over the chameleon, holding a slider, a toggle, a segmented control, a select, a stepper, three button styles and a badge.

That sheet is the kit with nothing custom on it: a slider, a toggle, a segmented control, a select, a stepper, the three button styles and a badge, sitting on a lg-panel at data-lg-bezel="30". The metrics are iOS ones, so a toggle is 51 by 31 and a slider thumb is a real bead of glass rather than a circle with a shadow under it.

Everything visual is markup plus a class from controls.css. The ones with behaviour have factories, and they all return the same shape:

import { slider, toggle, segmented, stepper, search, select } from 'lensglass';

const vol = slider({ min: 0, max: 1, value: 0.6, onChange: (v) => audio.volume = v });
document.body.appendChild(vol.el);

vol.value;          // read
vol.set(0.3);       // write, fires onChange
vol.set(0.3, true); // write quietly, for syncing from outside
vol.destroy();
  • slider({ min, max, step, value, label, ticks, fat, onChange }) — pointer capture, step snapping, arrows, Home/End, and RTL mirroring. ticks draws marks, fat is the volume/brightness variant where the track is the control.
  • toggle({ checked, label, onChange }) — 51 by 31, 27px knob, 20px travel.
  • segmented({ options, value, onChange }) — thumb placed by percentage, so it cannot mis-track when the page scrolls mid-transition.
  • stepper({ min, max, step, value, onChange }) — disables its own ends.
  • search({ placeholder, value, onInput, onSubmit }) — drawn magnifier, Escape clears, and the clear button only exists when there is something to clear.
  • select({ options, value, onChange }) — a button and a glass menu, because no platform lets you give a native <select> a material. Listbox roles, arrow keys, Escape, outside-click.
  • spinner(size), progress(value), dots(count, current), mark(size) — markup helpers. The spinner is the iOS activity indicator, eight spokes fading in sequence rather than a rotating arc.

Also in controls.css with no JS needed: .lg-btn in four variants and three sizes plus .lg-btn-round, .lg-field and .lg-chip and .lg-badge, .lg-check for checkbox and radio, .lg-progress, .lg-alert with iOS hairline action rows, and .lg-toast.

React

import { useLensGlass, Panel, Slider, Switch, Segmented, Button } from 'lensglass/react';
import 'lensglass/glass.css';
import 'lensglass/controls.css';

function Settings() {
  const tier = useLensGlass();
  const [vol, setVol] = useState(0.6);

  return (
    <Panel spec bezel={28}>
      <Slider value={vol} onChange={setVol} label="Volume" />
      <Switch checked={on} onChange={setOn} />
      <Segmented options={[{ id: 'a', label: 'Clear' }, { id: 'b', label: 'Frosted' }]} value={m} onChange={setM} />
      <Button variant="filled">Save</Button>
    </Panel>
  );
}

React is an optional peer; importing lensglass never touches it. The entry point ships as createElement calls rather than JSX, so there is no build step here either.

The interactive components mount the vanilla factories behind a display: contents wrapper instead of reimplementing them. That is on purpose. A slider has pointer capture, RTL mirroring, step snapping and keyboard handling in it, and writing that twice means maintaining two subtly different sliders and learning which one is wrong from a bug report.

Motion and comfort

prefers-reduced-motion flattens the transitions on every control and stops the spinner animating. prefers-reduced-transparency drops the whole thing to the solid tier, both from CSS and from the engine's own probe, so it holds even if your JavaScript never runs. prefers-contrast: more firms up the borders.

Browser support

Plain modern web platform: ES modules, canvas 2d, ResizeObserver, MutationObserver, mask-composite, color-mix. Any evergreen browser renders the material. Only Chromium bends light, as covered at the top.

Credit

The refraction approach follows Chris Feijoo's writeup, "Liquid Glass in the Browser: Refraction with CSS and SVG", at kube.io/blog/liquid-glass-css-svg, which is the same source the engine header cites. The optics here are traced rather than eyeballed: a circular lens profile sampled 128 times across the bezel, each sample refracted through Snell's law at n=1.5 and projected down to the backdrop plane.

If it looks like a smeared emboss rather than a magnifying rim, the displacement sign is inverted. Rays entering a denser medium bend toward the normal, so bezel pixels have to sample inward. That one sign is the difference between glass and a bevel filter from 1998.

The panther chameleon in the screenshots is a stock photograph, used here because fine repeating scale texture is the honest test of a lens: a fake one smears it. It is demo dressing and is not part of the published package.

License

MIT. Written for Mowzoon at a fintech hackathon, then pulled out and boxed on its own.