@dazecoop/liquid-glass 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Cooper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,257 @@
1
- # Temporary Holding Version
1
+ # Liquid Glass
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A "liquid glass" lens that refracts, frosts and splits the light of real HTML behind it: text, buttons, inputs, images. Move your cursor (or drag on touch) and the lens follows with spring physics, stretching as it moves. Everything under it stays selectable and clickable.
4
+
5
+ Built with vanilla JavaScript, SVG filters, [Vite](https://vite.dev) and [Tailwind CSS](https://tailwindcss.com).
6
+
7
+ ![Liquid Glass lens over a night-sky lake scene, with the tuning controls below](docs/animation.gif)
8
+
9
+ > [!NOTE]
10
+ > Live demo: [glass.dazemx.com](https://glass.dazemx.com/)
11
+
12
+ ## Features
13
+
14
+ - Rounded-rectangle lens with a curved, refracting rim, over live HTML
15
+ - Frosted blur, chromatic dispersion, brightness and saturation, and a soft drop shadow
16
+ - Lighting: rim light, a Blinn-Phong specular highlight and a fresnel edge glow
17
+ - Adjustable bevel depth, from a thin rim to a full magnifying dome
18
+ - Five presets: Clear, Frosted, Dark, Prism and Dome
19
+ - Glass can also sit behind an element as its background (the demo's dock), and button mode makes it brighten on hover and flatten on press
20
+ - Spring-driven movement with squash and stretch, plus a press effect
21
+ - Press <kbd>L</kbd> to lock the lens in place (handy for screenshots)
22
+ - Live controls for shape, glass and light, grouped in tabs
23
+ - Built-in backgrounds, or upload / drag and drop your own image
24
+ - The lens pops like a bubble when you scroll away from the hero, and swells back in when you return
25
+ - A code section that generates a drop-in snippet with your current settings
26
+ - Retina aware, touch friendly, keyboard nudging, respects `prefers-reduced-motion`
27
+
28
+ ## How it works
29
+
30
+ `backdrop-filter: url(#svg-filter)` would refract whatever is behind an element, but only Chrome supports it. Instead, the lens holds a copy of the page, lined up with the original, and bends that copy with an SVG displacement filter. Plain `filter: url()` works in every browser. The displacement map and rim lighting are generated from a signed-distance rounded rectangle with a circular bevel.
31
+
32
+ The copy refreshes whenever the page changes (a `MutationObserver` plus input and scroll events). It is `inert` and `aria-hidden`, so it doesn't affect find-in-page, focus or screen readers.
33
+
34
+ Safari needs two workarounds, both handled in [`src/glass.js`](src/glass.js):
35
+
36
+ - It drops an SVG filter when the filtered content contains GPU-composited layers, so the copy has `backdrop-filter`, `will-change` and animations stripped.
37
+ - It scales transformed content inside an SVG filter wrongly on retina screens, so the copy is positioned with `left`/`top`, and zoom scales the filtered layer from outside.
38
+
39
+ Two details keep the refraction steady as the lens moves:
40
+
41
+ - Zoom isn't stored in the displacement map. Its channels are 8-bit, so a zoom stored there moved in half-pixel steps that swept across the content. The filtered layer is built at `1 / zoom` size and scaled up instead, so the map only holds the rim.
42
+ - The lens snaps to whole CSS pixels. Browsers rasterise filters on the CSS pixel grid, so at a fractional position the refraction is resampled and jumps against the settled frame.
43
+
44
+ The demo page has a longer, illustrated walkthrough in its "How it works" section.
45
+
46
+ ## Browser support
47
+
48
+ Chrome, Firefox and Safari, on desktop and mobile.
49
+
50
+ Limitations of the copy: it doesn't show hover or focus states, `backdrop-filter` blur inside it is dropped (see above), and WebGL canvases and `<video>` aren't copied. IDs are removed from the copy so they stay unique, so style content inside the source with classes rather than `#id` selectors.
51
+
52
+ ## Install
53
+
54
+ ```sh
55
+ npm install @dazecoop/liquid-glass
56
+ ```
57
+
58
+ One package with three ways in. The core has no dependencies, and React is an optional peer.
59
+
60
+ | Import | What it is |
61
+ | --- | --- |
62
+ | `@dazecoop/liquid-glass` | `LiquidGlass`, plus the `GlassSwitch`, `GlassTabBar` and `GlassButton` components (ESM and CommonJS) |
63
+ | `@dazecoop/liquid-glass/react` | React components and a `useLiquidGlass` hook |
64
+ | `@dazecoop/liquid-glass/style.css` | Styles for the UI components (the lens itself needs none) |
65
+
66
+ TypeScript types are included.
67
+
68
+ ### React
69
+
70
+ ```jsx
71
+ import { GlassScene, LiquidGlass } from "@dazecoop/liquid-glass/react";
72
+
73
+ export function Hero() {
74
+ return (
75
+ // The lens follows the pointer over everything inside, refracting it
76
+ <GlassScene glass={{ ...LiquidGlass.presets.frosted, width: 240 }} style={{ height: 480 }}>
77
+ <h1>Liquid Glass</h1>
78
+ </GlassScene>
79
+ );
80
+ }
81
+ ```
82
+
83
+ Every component builds its glass after mount and destroys it on unmount, so StrictMode, route changes and server rendering are all fine. The file is marked `"use client"` for the Next.js app router.
84
+
85
+ | Component | Description |
86
+ | --- | --- |
87
+ | `<GlassScene glass>` | A lens that follows the pointer over its children, clipped to the scene |
88
+ | `<GlassPanel source button glass>` | An element with glass as its background, refracting `source` (a ref; its parent by default) |
89
+ | `<GlassSwitch checked onChange>` | The switch. Controlled, or uncontrolled with `defaultChecked` |
90
+ | `<GlassSegment options value onChange>` | A switch with a label each side: two `options`, as strings or `{ value, label }` |
91
+ | `<GlassTabBar items value onChange>` | The floating tab bar. `items` are `{ value, label, icon?, href? }` |
92
+ | `<GlassButton tint source>` | Clear glass that lights up in `tint`. Refracts its parent's `[data-glass-source]` child, or `source` |
93
+ | `useLiquidGlass(sourceRef, options)` | Returns the `LiquidGlass` instance for anything custom. Pass `anchor` or `container` as refs |
94
+
95
+ Components take a `look` prop to override their glass parameters, and pass other props (`className`, `style`, `aria-*`) to their element.
96
+
97
+ ### JavaScript
98
+
99
+ ```js
100
+ import { LiquidGlass } from "@dazecoop/liquid-glass";
101
+
102
+ // The lens refracts everything inside this element
103
+ const scene = document.querySelector("main");
104
+ const glass = new LiquidGlass(scene, { container: scene });
105
+
106
+ glass.set({ width: 280, height: 180, radius: 64 });
107
+
108
+ scene.addEventListener("pointermove", (e) => glass.moveTo(e.clientX, e.clientY));
109
+ scene.addEventListener("pointerdown", () => glass.press(true));
110
+ scene.addEventListener("pointerup", () => glass.press(false));
111
+
112
+ // When it's no longer needed
113
+ glass.destroy();
114
+ ```
115
+
116
+ To give an element a glass background instead, anchor the glass to it. Add `button: true` to make it respond to hover and press:
117
+
118
+ ```js
119
+ new LiquidGlass(scene, { anchor: document.querySelector("#panel"), refraction: 12, chroma: 0 });
120
+ new LiquidGlass(scene, { anchor: document.querySelector("#play"), button: true });
121
+ ```
122
+
123
+ Start from a preset and override what you like:
124
+
125
+ ```js
126
+ const glass = new LiquidGlass(scene, { ...LiquidGlass.presets.frosted, frost: 8 });
127
+ glass.set(LiquidGlass.presets.dark);
128
+ ```
129
+
130
+ ### Script tag
131
+
132
+ No install and no build step. The script defines one global, `LiquidGlass`:
133
+
134
+ ```html
135
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@dazecoop/liquid-glass@0/lib/liquid-glass.css" />
136
+ <script src="https://cdn.jsdelivr.net/npm/@dazecoop/liquid-glass@0/lib/liquid-glass.global.js"></script>
137
+ <script>
138
+ const glass = new LiquidGlass(document.querySelector("main"));
139
+ LiquidGlass.initComponents(); // any switches, tab bars and buttons in the page
140
+ </script>
141
+ ```
142
+
143
+ You can also tune the lens on the [demo](https://glass.dazemx.com/) and copy the code from its **Use it anywhere** section, written for React, JavaScript, a script tag, or as one self-contained file.
144
+
145
+ ## UI components
146
+
147
+ A switch, a segmented switch, a floating tab bar and a glass button, after iOS 26. Import `style.css` once. In React, use the components above. In HTML, write the markup and call `initComponents()`:
148
+
149
+ ```html
150
+ <button type="button" role="switch" aria-checked="true" aria-label="Wi-Fi" class="lg-switch" data-glass-switch>
151
+ <span class="refract"></span><span class="thumb"></span><span data-opt></span><span data-opt></span>
152
+ </button>
153
+
154
+ <div role="radiogroup" aria-label="Billing" class="lg-segment" data-glass-switch>
155
+ <span class="refract"></span><span class="thumb"></span>
156
+ <button type="button" role="radio" aria-checked="true" data-opt>Monthly</button>
157
+ <button type="button" role="radio" aria-checked="false" data-opt>Yearly</button>
158
+ </div>
159
+
160
+ <nav class="lg-tabs" aria-label="Main" data-glass-tabs>
161
+ <span class="lg-pill" hidden></span>
162
+ <span class="lg-glowbox"><span class="lg-glow"></span></span>
163
+ <button type="button" aria-current="page">Home</button>
164
+ <button type="button">Search</button>
165
+ </nav>
166
+
167
+ <!-- The button's lens lives in its (positioned) parent and refracts the [data-glass-source] -->
168
+ <div style="position:relative">
169
+ <div data-glass-source><img src="map.jpg" alt="" /></div>
170
+ <button type="button" class="lg-button" data-glass-button><span class="lg-content">Add stop</span></button>
171
+ </div>
172
+ ```
173
+
174
+ ```js
175
+ import { initComponents } from "@dazecoop/liquid-glass";
176
+
177
+ const components = initComponents(); // or initComponents(someElement)
178
+ // Later: components.forEach((c) => c.destroy());
179
+ ```
180
+
181
+ Switches and tab bars fire a bubbling `change` event (`event.detail.value` is 0 or 1 for a switch, `event.detail.tab` is the element for a tab bar).
182
+
183
+ The dark look is the default. Put `data-theme="light"` on any ancestor for the light one. Every colour is a custom property (`--lg-accent`, `--lg-on`, `--lg-surface` and so on, listed at the top of [`src/liquid-glass.css`](src/liquid-glass.css)), so a theme is a handful of overrides. `--lg-surface` should match the colour right behind a component.
184
+
185
+ ## API
186
+
187
+ | Method | Description |
188
+ | --- | --- |
189
+ | `new LiquidGlass(source, options)` | Creates a lens over `source`. Options: `anchor` (an element to fill as its background), `container` (an element for the lens to live in, clipped to it), `button: true` (anchored glass responds to hover and press), `throttle` (re-copy a large source at most every this many ms), plus any parameters below. |
190
+ | `LiquidGlass.presets` | `clear`, `frosted`, `dark`, `prism` and `dome`, each a set of parameters. |
191
+ | `set(params)` | Updates any of the parameters below. |
192
+ | `moveTo(x, y)` | Moves the lens to a point in viewport (client) coordinates. |
193
+ | `press(down)` | Grows the lens slightly while `true`. |
194
+ | `show(visible, { pop })` | Fades the lens in or out. With `pop: true` it bursts like a bubble on the way out and swells back in. `moveTo` shows it again. |
195
+ | `destroy()` | Removes the glass and everything it added to the page, and stops its listeners and observers. Safe to call twice. The components have `destroy()` too. |
196
+
197
+ | Parameter | Default | Description |
198
+ | --- | --- | --- |
199
+ | `width`, `height` | `280`, `180` | Lens size in CSS pixels (anchored glass takes its anchor's size) |
200
+ | `radius` | `64` | Corner radius in CSS pixels (anchored glass takes its anchor's) |
201
+ | `bevel` | `35` | How far the curved rim reaches in, in CSS pixels. At half the lens's smaller side or more, it's a full dome |
202
+ | `refraction` | `40` | How far the rim bends the content, in CSS pixels |
203
+ | `frost` | `2` | Blur radius in CSS pixels |
204
+ | `chroma` | `0.4` | Chromatic dispersion at the rim, `0` to `1` |
205
+ | `zoom` | `1.08` | Magnification across the whole lens |
206
+ | `specular` | `0.35` | Strength of the specular highlight, `0` to `1` |
207
+ | `fresnel` | `0.3` | Strength of the edge glow where the glass turns away, `0` to `1` |
208
+ | `brightness` | `0` | Brightness of what's seen through the glass, `-0.5` to `0.5` |
209
+ | `saturation` | `0` | Saturation of what's seen through the glass, `-1` (grey) to `1` |
210
+ | `tint` | `""` | A CSS colour laid over the glass, for legibility |
211
+ | `rim` | `1` | Strength of the rim lighting and edge hairline, `0` to `1` |
212
+
213
+ ## Development
214
+
215
+ ```sh
216
+ bun install
217
+ bun run dev
218
+ ```
219
+
220
+ Then open the URL Vite prints. `bun run build` outputs the demo site to `dist/`, and `bun run build:lib` outputs the package to `lib/`.
221
+
222
+ npm works too: `npm install && npm run dev`.
223
+
224
+ ## Project structure
225
+
226
+ ```
227
+ index.html Demo page markup (Tailwind)
228
+ src/glass.js LiquidGlass: the copy, SVG filter, displacement map and spring physics
229
+ src/components.js GlassSwitch, GlassTabBar and GlassButton
230
+ src/liquid-glass.css The components' styles (the package's style.css)
231
+ src/react.jsx React bindings
232
+ src/index.js Package entry; src/global.js is the script tag's
233
+ types/ TypeScript definitions
234
+ src/main.js Demo UI: backgrounds, presets, sliders, pointer handling, page sections
235
+ src/developer.js The demo's code guide and snippet generator
236
+ src/style.css Demo styles (Tailwind)
237
+ ```
238
+
239
+ ## Credits
240
+
241
+ Inspired by [this CodePen by shingidad](https://codepen.io/shingidad/pen/jEPxMgW), which this project started as a rebuild of.
242
+
243
+ Lighting, tone, presets, the dome bevel and button mode are adapted from [ybouane/liquidglass](https://github.com/ybouane/liquidglass).
244
+
245
+ Background photos from [Pexels](https://www.pexels.com).
246
+
247
+ Not affiliated with Apple.
248
+
249
+ ## Support
250
+
251
+ If my projects have been useful to you and you'd like to support the work, you can buy me a coffee. Completely optional, but always appreciated!
252
+
253
+ <a href="https://www.buymeacoffee.com/dazecoop" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me a Coffee" style="height: 60px !important;width: 217px !important;" ></a>
254
+
255
+ ## Licence
256
+
257
+ MIT. See [LICENSE](LICENSE).