@aberhamm/liquid-glass-react 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,627 +1,85 @@
1
1
  # @aberhamm/liquid-glass-react
2
2
 
3
- Liquid-glass UI primitives and batteries-included components for React — a
4
- frosted, refractive "glass" surface driven by `backdrop-filter` and SVG
5
- displacement filters, with graceful per-engine degradation and zero runtime
6
- dependencies.
3
+ React glass components powered by the pinned
4
+ [`@samasante/liquid-glass`](https://github.com/samasante/liquid-glass) engine.
5
+ This package owns the composed controls and their styling; the optics engine is
6
+ maintained upstream. Matui imports this package rather than maintaining a second
7
+ copy of the components.
7
8
 
8
- > **Independent reimplementation, not a fork.** This library reimplements the
9
- > liquid-glass technique popularized by
10
- > [`rdev/liquid-glass-react`](https://github.com/rdev/liquid-glass-react) and
11
- > [`shuding/liquid-glass`](https://github.com/shuding/liquid-glass). It shares
12
- > the *prop surface and intent* — not the code. No source was copied; see
13
- > [`LICENSE`](./LICENSE) and [`docs/PARITY.md`](./docs/PARITY.md).
14
-
15
- - **Frosted glass by default, everywhere** — a portable `backdrop-filter` surface
16
- (blur + saturate + inset-shadow glass edge + rim + elastic motion) that renders
17
- the same in Chrome, Safari/WebKit, and Firefox. This is the default look.
18
- - **Opt-in live-backdrop refraction (`displacement`)** — Chromium-only SVG
19
- `feDisplacementMap` composited over the live backdrop via `backdrop-filter:
20
- url()`, plus chromatic aberration. Silently falls back to frosted on
21
- Firefox/Safari.
22
- - **Opt-in cross-browser refraction (`refract` / `size` / `center`)** — applies
23
- the same SVG displacement as an element `filter: url()` on a *copy* of content,
24
- so a real refractive lens renders in Chrome, Safari **and** Firefox.
25
- - **Web + Expo/React Native from one install** — the same `<LiquidGlass>` import
26
- resolves to a native (`expo-blur`) frosted build under Metro. iOS is the quality
27
- bar; Android is best-effort.
28
- - **Identical box geometry across every tier** — degrading never shifts layout.
29
- - **SSR-safe** — conservative capabilities until mount, no hydration mismatch.
30
- - **Zero runtime dependencies.** React 18+ is a peer dependency; the Expo peer
31
- deps are optional and web consumers never install them.
32
-
33
- ---
34
-
35
- ## Install
36
-
37
- ```bash
9
+ ```sh
38
10
  pnpm add @aberhamm/liquid-glass-react
39
- # or
40
- npm install @aberhamm/liquid-glass-react
41
- # or
42
- yarn add @aberhamm/liquid-glass-react
43
- ```
44
-
45
- `react` and `react-dom` `>=18` are **peer dependencies** — install them in your
46
- app if you haven't already:
47
-
48
- ```bash
49
- pnpm add react react-dom
50
- ```
51
-
52
- ### CSS import
53
-
54
- The **prebuilt components** (`GlassButton`, `GlassCard`,
55
- `GlassSegmentedControl`) require their stylesheet, imported **once** anywhere in
56
- your app (e.g. your root layout or entry):
57
-
58
- ```ts
59
- import '@aberhamm/liquid-glass-react/styles.css';
60
11
  ```
61
12
 
62
- The low-level `<LiquidGlass>` **primitive does not need the stylesheet** — it is
63
- fully self-contained and works without it.
64
-
65
- ---
66
-
67
- ## Quick start
68
-
69
- ### `LiquidGlass` (the primitive)
70
-
71
13
  ```tsx
72
- import { LiquidGlass } from '@aberhamm/liquid-glass-react';
73
-
74
- export function Badge() {
75
- return (
76
- <LiquidGlass cornerRadius={24} padding="16px 24px">
77
- <span style={{ color: 'white', fontWeight: 600 }}>Liquid glass</span>
78
- </LiquidGlass>
79
- );
80
- }
81
- ```
82
-
83
- ### `GlassButton`
84
-
85
- ```tsx
86
- import { GlassButton } from '@aberhamm/liquid-glass-react';
14
+ import {
15
+ Glass, GlassButton, GlassCard, GlassSwitch, GlassSlider,
16
+ GlassSegmentedControl, GlassNotification,
17
+ } from '@aberhamm/liquid-glass-react';
87
18
  import '@aberhamm/liquid-glass-react/styles.css';
88
19
 
89
- export function Actions() {
90
- return (
91
- <GlassButton variant="primary" size="md" onClick={() => alert('clicked')}>
92
- Get started
93
- </GlassButton>
94
- );
95
- }
96
- ```
97
-
98
- ### `GlassCard`
99
-
100
- ```tsx
101
- import { GlassCard } from '@aberhamm/liquid-glass-react';
102
- import '@aberhamm/liquid-glass-react/styles.css';
103
-
104
- export function Panel() {
105
- return (
106
- <GlassCard elevation="floating">
107
- <h3>Frosted panel</h3>
108
- <p>Content stays legible over busy backgrounds.</p>
109
- </GlassCard>
110
- );
111
- }
112
- ```
113
-
114
- ### `GlassSegmentedControl`
115
-
116
- ```tsx
117
- import { useState } from 'react';
118
- import { GlassSegmentedControl } from '@aberhamm/liquid-glass-react';
119
- import '@aberhamm/liquid-glass-react/styles.css';
120
-
121
- export function ViewToggle() {
122
- const [value, setValue] = useState('grid');
123
- return (
124
- <GlassSegmentedControl
125
- label="View mode"
126
- value={value}
127
- onValueChange={setValue}
128
- options={[
129
- { value: 'grid', label: 'Grid' },
130
- { value: 'list', label: 'List' },
131
- { value: 'map', label: 'Map' },
132
- ]}
133
- />
134
- );
135
- }
136
- ```
137
-
138
- The control is a **native radiogroup** (`<fieldset>` + visually-hidden
139
- `<input type="radio">`), so Arrow/Home/End/Space keyboard navigation and
140
- screen-reader semantics work for free. Pass `label` (or `aria-label`) for the
141
- accessible group name; set `showLabel` to render it visually.
142
-
143
- ---
144
-
145
- ## Web usage: frosted default, opt-in refraction
146
-
147
- On the web the glass renders a **portable frosted surface by default** — the same
148
- `backdrop-filter` blur/saturate look in Chrome, Safari/WebKit, and Firefox. No
149
- props are required:
150
-
151
- ```tsx
152
- import { LiquidGlass } from '@aberhamm/liquid-glass-react';
153
-
154
- <LiquidGlass cornerRadius={24} padding="16px 24px">
155
- <span>Frosted by default — portable across engines</span>
156
- </LiquidGlass>;
157
- ```
158
-
159
- There are **two independent, opt-in ways** to add real refraction. They compose,
160
- and both are off by default.
161
-
162
- ### `displacement` — bend the LIVE page (Chromium only)
163
-
164
- `displacement` attaches the SVG `feDisplacementMap` as a `backdrop-filter: url()`,
165
- so it warps the **live content behind** the glass. This only composites in
166
- Chromium (Chrome, Edge, Brave, Opera); Firefox and Safari/WebKit **silently fall
167
- back** to the frosted surface.
168
-
169
- ```tsx
170
- // Chromium bends the real page behind the glass; other engines stay frosted.
171
- <LiquidGlass displacement>
172
- <span>Live-backdrop refraction where supported</span>
173
- </LiquidGlass>;
174
- ```
175
-
176
- > **Upgrading from `0.1.x`?** Refraction used to be the default look. Add
177
- > `displacement` to restore the old Chromium live-backdrop bend.
178
-
179
- ### `refract` / `size` / `center` — refract a COPY (cross-browser)
180
-
181
- The cross-browser path applies the same SVG displacement as an **element**
182
- `filter: url()` on a **copy** of content. Because it filters an element (not the
183
- backdrop), a real refractive lens renders in Chrome, Safari **and** Firefox. Two
184
- modes:
185
-
186
- - **In-place** — pass `size` and/or `center` to refract a copy of the glass's own
187
- children, with the crisp children on top:
188
-
189
- ```tsx
190
- <LiquidGlass size={{ width: 320, height: 200 }} center={{ x: 0.5, y: 0.5 }}>
191
- <span>Refracts a copy of its own children — all engines</span>
192
- </LiquidGlass>;
193
- ```
194
-
195
- - **Lens over foreign content** — pass `refract={node}` to float a lens over
196
- content the glass doesn't own; `behind` fills any bleed the copied node doesn't
197
- cover:
198
-
199
- ```tsx
200
- <LiquidGlass refract={<img src="/poster.jpg" alt="" />} behind="#0b0b0f">
201
- <span>Floats a refractive lens over the poster</span>
202
- </LiquidGlass>;
203
- ```
204
-
205
- > **Which bends what?** `displacement` bends the **live page** behind the glass
206
- > (Chromium only). `refract` / `size` / `center` refract a **copy** of content via
207
- > an element filter (Chrome, Safari, **and** Firefox). They are distinct and
208
- > compose. The refracted copy is decorative — `aria-hidden` + `inert` +
209
- > `pointer-events: none` — so keep the copied subtree **purely presentational**
210
- > (no `id`s, effects, focusable controls, or data fetching; it mounts twice).
211
-
212
- ---
213
-
214
- ## Expo / React Native
215
-
216
- The **same `<LiquidGlass>` import** works in an Expo / React Native app. Metro
217
- resolves the native build automatically via the package's `react-native` export
218
- condition — there is no separate import path:
219
-
220
- ```tsx
221
- import { LiquidGlass } from '@aberhamm/liquid-glass-react';
222
- import { Text } from 'react-native';
223
-
224
- export function Badge() {
225
- return (
226
- <LiquidGlass cornerRadius={24} padding={16} onPress={() => {}}>
227
- <Text style={{ color: 'white', fontWeight: '600' }}>Liquid glass</Text>
228
- </LiquidGlass>
229
- );
230
- }
231
- ```
232
-
233
- The native surface renders **frosted glass** — an `expo-blur` `BlurView` with a
234
- tint and a gradient sheen. It is a genuinely different renderer from the web build,
235
- so the web-only props are **documented no-ops** on native: `displacement`,
236
- `refract`, `size`, `center`, `behind`, `saturation`, `variant`, `mode`,
237
- `aberrationIntensity`, and the pointer/motion props. The native primitive uses
238
- `onPress` (the web primitive uses `onClick`) and takes `style?: ViewStyle`.
239
-
240
- > **iOS is the quality bar; Android is best-effort.** Android uses expo-blur's
241
- > `experimentalBlurMethod` and degrades gracefully where blur support is limited.
242
-
243
- ### Optional peer deps
244
-
245
- The Expo target needs two **optional** peer dependencies — `expo-blur` and
246
- `expo-linear-gradient` — plus `react-native` (and `expo` itself), which your Expo
247
- app already provides. They are marked `optional` in `peerDependenciesMeta`, so
248
- **web-only consumers never install them**:
249
-
250
- ```bash
251
- npx expo install expo-blur expo-linear-gradient
252
- # react-native and expo come from your Expo app itself
253
- ```
254
-
255
- The prebuilt `GlassButton`, `GlassCard`, and `GlassSegmentedControl` are
256
- **web-only this round** — they are DOM components and are intentionally **not**
257
- part of the native barrel (`src/index.native.ts`), so they won't resolve in a
258
- native bundle. Use the `<LiquidGlass>` primitive on native.
259
-
260
- ---
261
-
262
- ## What works where
263
-
264
- | Capability | Chrome / Edge | Safari / WebKit | Firefox | Expo (iOS) | Expo (Android) |
265
- | --- | --- | --- | --- | --- | --- |
266
- | **Frosted glass** (default) | ✅ | ✅ | ✅ | ✅ | ⚠️ best-effort |
267
- | **`displacement`** (live-backdrop bend) | ✅ | frosted fallback | frosted fallback | no-op | no-op |
268
- | **Cross-browser refraction** (`refract` / `size` / `center`) | ✅ | ✅ | ✅ | no-op | no-op |
269
- | **Prebuilt** `GlassButton` / `GlassCard` / `GlassSegmentedControl` | ✅ | ✅ | ✅ | ❌ web-only | ❌ web-only |
270
-
271
- Frosted is the portable default everywhere. `displacement` bends the live page and
272
- is Chromium-only (frosted fallback elsewhere). The cross-browser `refract` path
273
- renders a refractive copy in all three web engines. The prebuilt components are
274
- web-only this round.
275
-
276
- ---
277
-
278
- ## The `asChild` polymorphism pattern
279
-
280
- `GlassButton` and `GlassCard` accept `asChild`. When set, the component renders
281
- **your** single child element instead of its default tag (`<button>` / `<div>`),
282
- merging its props, `className`, and `ref` onto it. The child then owns its own
283
- semantics and accessibility — ideal for link-styled buttons:
284
-
285
- ```tsx
286
- import { GlassButton } from '@aberhamm/liquid-glass-react';
287
-
288
- <GlassButton asChild variant="secondary">
289
- <a href="/docs">Read the docs</a>
290
- </GlassButton>;
291
- ```
292
-
293
- ---
294
-
295
- ## Capability detection
296
-
297
- Use the hook (or the underlying detector) to branch on what the current engine
298
- can actually render. The single gate is `canRefract` — `true` only when the full
299
- SVG-displacement refraction will composite (Chromium with `backdrop-filter`):
300
-
301
- ```tsx
302
- import { useGlassCapabilities } from '@aberhamm/liquid-glass-react';
303
-
304
- function Hint() {
305
- const caps = useGlassCapabilities();
306
- return caps.canRefract
307
- ? <p>Full refraction is active.</p>
308
- : <p>Showing the frosted fallback for this browser.</p>;
309
- }
310
- ```
311
-
312
- During SSR and before the mount effect runs, every capability is conservatively
313
- `false` (so the server and first client render agree). For one-shot,
314
- non-reactive checks there is also `detectGlassCapabilities()`:
315
-
316
- ```ts
317
- import { detectGlassCapabilities } from '@aberhamm/liquid-glass-react';
318
-
319
- const caps = detectGlassCapabilities(); // GlassCapabilities
320
- if (caps.canRefract) {
321
- /* ... */
322
- }
323
- ```
324
-
325
- `GlassCapabilities` fields: `supportsBackdropFilter`, `isChromium`,
326
- `supportsSvgBackdropDisplacement`, `isFirefox`, `prefersReducedMotion`, and the
327
- derived `canRefract`.
328
-
329
- ---
330
-
331
- ## Browser support
332
-
333
- The **default** render is the portable frosted surface — identical in Chrome,
334
- Safari/WebKit, and Firefox. The runtime `canRefract` gate
335
- (`supportsBackdropFilter && supportsSvgBackdropDisplacement`, where the latter is
336
- positive Blink-family detection) only decides what the **opt-in `displacement`**
337
- prop does: whether the live-backdrop SVG bend composites, or degrades to frosted.
338
- The cross-browser `refract` / `size` / `center` path does **not** depend on this
339
- gate — it renders in every engine. All tiers share **identical box geometry**, so
340
- moving between them causes **no layout shift**. See
341
- [`docs/PARITY.md`](./docs/PARITY.md) for the full contract.
342
-
343
- Behavior when `displacement` is requested:
344
-
345
- | Engine | `canRefract` | With `displacement` | Default (no `displacement`) |
346
- | --- | --- | --- | --- |
347
- | **Chromium** (Chrome, Edge, Brave, Opera) | `true` | **Live-backdrop refraction** — SVG `feDisplacementMap` bends the live page behind the glass, over `backdrop-filter` blur/saturate, with chromatic aberration and elastic motion. | Frosted surface. |
348
- | **Firefox** (Gecko) | `false` | **Frosted fallback** — `displacement` degrades silently; no live-backdrop bend. | Frosted surface. |
349
- | **Safari / WebKit** | `false` | **Frosted fallback** — same as Firefox; WebKit does not composite the SVG displacement over the backdrop. | Frosted surface. |
350
- | No `backdrop-filter` (very old engines) | `false` | **Solid translucent fallback** — scheme-aware `rgba(...)` fill so content stays legible; never a transparent box. | Solid translucent fallback. |
351
- | **SSR / pre-mount** | `false` | **Conservative (frosted/degraded)** — all capabilities `false` until the client mount effect re-evaluates, avoiding hydration mismatch. | Conservative. |
352
-
353
- The cross-browser `refract` / `size` / `center` path renders a refractive **copy**
354
- in all of the above web engines regardless of `canRefract` (see
355
- [Web usage](#refract--size--center--refract-a-copy-cross-browser)).
356
-
357
- Why not feature-detect refraction directly? There is no standardized
358
- `CSS.supports` probe for "an SVG `feDisplacementMap` composites correctly over
359
- `backdrop-filter`" — it's a Chromium rendering-pipeline quirk. The library uses
360
- positive Blink detection (a pragmatic, revisitable heuristic) to keep Safari and
361
- Firefox in the frosted tier by construction. Details and caveats live in
362
- [`docs/PARITY.md`](./docs/PARITY.md).
363
-
364
- ---
365
-
366
- ## Displacement `mode`
367
-
368
- The primitive's `mode` prop selects the displacement algorithm used to generate
369
- the SVG filter (full effect renders in Chromium; other engines show the frosted
370
- fallback regardless of `mode`):
371
-
372
- | Mode | Look |
373
- | --- | --- |
374
- | `standard` *(default)* | Balanced, edge-weighted displacement — the default glass look. |
375
- | `polar` | Radial/polar displacement, stronger toward the perimeter. |
376
- | `prominent` | Exaggerated displacement for a heavier "thick glass" feel. |
377
- | `shader` | Shader-style profile for sharper highlights. The displacement map is **generated at runtime** (canvas → `data:` URL feeding an `feImage`). |
378
- | `turbulence` | Adds fractal `feTurbulence` for an organic, watery, **procedural frosted-ripple** distortion. |
379
-
380
- ```tsx
381
- <LiquidGlass mode="turbulence">…</LiquidGlass>
382
- ```
383
-
384
- ---
385
-
386
- ## Content-adaptive auto-tint (`adaptiveTint`)
387
-
388
- Apple's Liquid Glass adapts its tint and content treatment to the brightness of
389
- whatever is behind it. `adaptiveTint` brings that to `<LiquidGlass>`: when `true`
390
- the glass samples the luminance of its backdrop and automatically shifts toward a
391
- **light** or **dark** treatment — the SAME tint / displacement / blur plumbing
392
- `overLight` already drives — so foreground content (labels, captions) stays
393
- legible without hand-tuning. A bright backdrop yields the light treatment with
394
- dark ink; a dark backdrop yields the default treatment with light ink.
395
-
396
- ```tsx
397
- // No overLight needed — the glass figures out light vs dark for you.
398
- <LiquidGlass adaptiveTint>
399
- <span>Adapts to its backdrop</span>
400
- </LiquidGlass>
401
- ```
402
-
403
- It is **additive and opt-in**: `adaptiveTint` defaults to `false`, so the default
404
- render is byte-for-byte unchanged and the luminance sampler is never loaded on the
405
- default path.
406
-
407
- **Precedence — `overLight` always wins.** `overLight` is the manual override;
408
- `adaptiveTint` is the auto path. They never fight. When `overLight` is set
409
- explicitly it short-circuits the auto path:
410
-
411
- ```ts
412
- effectiveOverLight = overLight ?? (adaptiveTint && scheme ? scheme === 'light' : false)
413
- ```
414
-
415
- **SSR / hydration-safe.** The server and the first client paint render the
416
- default (unsampled) treatment, so hydration never mismatches; the sampled
417
- treatment is applied in an effect after mount.
418
-
419
- **Graceful degradation.** When the backdrop can't be sampled — a **cross-origin**
420
- backdrop taints the canvas, there's no canvas, or it's SSR — the reading is
421
- `sampled: false` and auto-tint silently falls back to `overLight ?? false`. No
422
- error, no flicker loop, no `console.error`.
423
-
424
- **Accessibility.** Under `(prefers-contrast: more)` the increased-contrast
425
- treatment **wins**: auto-tint never undercuts the high-contrast surface or
426
- legibility treatment.
427
-
428
- > ⚠️ **Limitation.** Auto-tint is **best-effort legibility**. Critical text over
429
- > unknown or cross-origin backdrops (which cannot be sampled) should be verified
430
- > manually — the auto path falls back rather than guessing.
431
-
432
- ---
433
-
434
- ## Scroll-aware shadow (`scrollAwareShadow`)
435
-
436
- Apple's Liquid Glass deepens a pinned bar's drop-shadow as content scrolls
437
- beneath it — lifting it above the text — and eases it over solid backgrounds.
438
- `scrollAwareShadow` brings that to `<LiquidGlass>`: when `true` the glass reuses
439
- the **same** backdrop-luminance sampler as `adaptiveTint` and modulates **only**
440
- its decoupled drop-shadow — **deeper and darker** over a dark/dense backdrop,
441
- **shallower and lighter** over a light/solid one.
442
-
443
- ```tsx
444
- // The drop-shadow tracks the backdrop as content scrolls beneath the bar.
445
- <LiquidGlass scrollAwareShadow>
446
- <span>Pinned toolbar</span>
447
- </LiquidGlass>
448
- ```
449
-
450
- It is **additive and opt-in**: `scrollAwareShadow` defaults to `false`, so the
451
- default render is byte-for-byte unchanged and the sampler is never loaded on the
452
- default path. Only the shadow's **blur / offset / opacity** vary — it stays the
453
- decoupled sibling behind the clipped surface (never a `box-shadow` on the clipped
454
- node), so no clipping eats it and no new layer is added.
455
-
456
- **SSR / hydration-safe.** The server and the first client paint render the
457
- conservative static shadow; the modulated shadow is applied in an effect after
458
- mount, so hydration never mismatches.
459
-
460
- **Graceful degradation.** When the backdrop can't be sampled (cross-origin taint,
461
- no canvas, SSR) the reading is `sampled: false` and the shadow falls back to the
462
- static one — no error, no flicker loop, no `console.error`.
463
-
464
- **Reduced motion.** Under `(prefers-reduced-motion: reduce)` the shadow **snaps**
465
- between depths instead of animating (the `box-shadow` transition is dropped).
466
-
467
- ---
468
-
469
- ## Material variants: Regular vs Clear (`variant`)
470
-
471
- Apple distinguishes two Liquid Glass materials, and `variant` lets you pick one
472
- intentionally instead of hand-tuning opacity:
473
-
474
- - **`regular`** (default) — the dependable, fully adaptive control surface. This
475
- is **exactly** today's behavior, including content-adaptive auto-tint when
476
- [`adaptiveTint`](#content-adaptive-auto-tint-adaptivetint) is on.
477
- - **`clear`** — a permanently **more transparent** material for media-rich
478
- contexts (floating over a photo or video). It is **non-adaptive by
479
- definition**, with a subtle dimming scrim behind the content so labels stay
480
- legible over busy media.
481
-
482
- ```tsx
483
- // Dependable control surface (default).
484
- <LiquidGlass variant="regular">
485
- <span>Toolbar</span>
486
- </LiquidGlass>
487
-
488
- // Maximally transparent over media — clearer, with a legibility scrim.
489
- <LiquidGlass variant="clear">
490
- <span>Over a photo</span>
491
- </LiquidGlass>
492
- ```
493
-
494
- It is **additive and non-breaking**: `variant` defaults to `'regular'`, so
495
- omitting it keeps today's render byte-for-byte unchanged. It is a small parameter
496
- lookup feeding the existing surface/content styles — not a theming system.
497
-
498
- **Don't mix — Clear is non-adaptive.** Per Apple's guidance the two should never
499
- be mixed in the same context. In `'clear'`:
500
-
501
- - **`adaptiveTint` is a no-op** — Clear never samples the backdrop or flips its
502
- tint/ink. (Use `'regular'` + `adaptiveTint` for the adaptive material.)
503
- - **`overLight` still nudges legibility**, but does NOT re-enable adaptivity.
504
-
505
- **Accessibility wins in both variants.** Under `(prefers-contrast: more)` the
506
- increased-contrast treatment (solid border, opaque fill, pinned saturation)
507
- applies to Clear too — a11y beats the Clear aesthetic.
508
-
509
- The prebuilt components (`GlassButton`, `GlassCard`, `GlassSegmentedControl`)
510
- take `variant` through their `glassProps` escape hatch — no new per-component
511
- prop:
512
-
513
- ```tsx
514
- <GlassButton glassProps={{ variant: 'clear' }}>Over media</GlassButton>
515
- ```
516
-
517
- ---
518
-
519
- ## API reference
520
-
521
- ### `<LiquidGlass>` (primitive)
522
-
523
- | Prop | Type | Default | Description |
524
- | --- | --- | --- | --- |
525
- | `children` | `ReactNode` | — | Content rendered inside the glass surface. |
526
- | `displacement` | `boolean` | `false` | Opt into live-backdrop SVG refraction (**Chromium only**; frosted fallback on Firefox/Safari). Bends the real page behind the glass via `backdrop-filter: url()`. No-op on native. |
527
- | `displacementScale` | `number` | `70` | Strength of the refraction distortion; higher bends the backdrop more. |
528
- | `blurAmount` | `number` | `0.0625` | Backdrop blur radius (px) applied behind the glass. |
529
- | `saturation` | `number` | `140` | Backdrop saturation multiplier (`1` = unchanged). |
530
- | `aberrationIntensity` | `number` | `2` | Chromatic aberration (RGB separation) at refracted edges; `0` disables. |
531
- | `elasticity` | `number` | `0.15` | Pointer-follow softness; `0` is rigid, higher is rubbery. |
532
- | `cornerRadius` | `number \| string` | `999` | Corner radius. Number = px; string = CSS length (e.g. `'1rem'`, `'50%'`). |
533
- | `padding` | `number \| string` | `'24px 32px'` | Inner padding. Number = px; string = CSS shorthand. |
534
- | `overLight` | `boolean` | `false` | Hint that the glass sits over a light background; tunes tint/contrast. The manual override — always wins over `adaptiveTint`. |
535
- | `adaptiveTint` | `boolean` | `false` | Opt into content-adaptive auto-tint: samples the backdrop and auto-shifts light/dark for legibility (see [Content-adaptive auto-tint](#content-adaptive-auto-tint-adaptivetint)). |
536
- | `scrollAwareShadow` | `boolean` | `false` | Opt in: the decoupled drop-shadow deepens/darkens over dark/dense backdrops and eases/lightens over light/solid ones, from the same backdrop sampler (see [Scroll-aware shadow](#scroll-aware-shadow-scrollawareshadow)). |
537
- | `variant` | `'regular' \| 'clear'` | `'regular'` | Material variant. `regular` = today's fully adaptive surface; `clear` is permanently more transparent and non-adaptive (`adaptiveTint` is a no-op) with a dimming scrim (see [Material variants](#material-variants-regular-vs-clear-variant)). |
538
- | `mode` | `DisplacementMode` | `'standard'` | Displacement algorithm (see [Displacement `mode`](#displacement-mode)). |
539
- | `refract` | `ReactNode` | `undefined` | **Cross-browser** copy-refraction: floats a refracted lens rendering this node (Chrome, Safari, Firefox). The copy is `aria-hidden` + `inert` + `pointer-events:none` — keep it presentational. No-op on native. |
540
- | `size` | `GlassSize` (`{ width: number; height: number }`) | measured | Explicit lens size in px. Setting it (with or without `refract`) opts into **in-place** cross-browser copy-refraction of the glass's own children. No-op on native. |
541
- | `center` | `GlassCenter` (`{ x: number; y: number }`) | `{ x: 0.5, y: 0.5 }` | Lens focal point as fractions in `[0, 1]`; drives the refracted copy's `transform-origin`. Opts into in-place copy-refraction like `size`. No-op on native. |
542
- | `behind` | `string` | `undefined` | CSS `background` painted behind the refracted copy to fill bleed (most useful with `refract`). No effect unless copy-refraction is active. No-op on native. |
543
- | `className` | `string` | — | Class name(s) on the outermost glass element. |
544
- | `style` | `CSSProperties` | — | Inline styles merged onto the outermost element. |
545
- | `onClick` | `MouseEventHandler<HTMLDivElement>` | — | Click handler forwarded to the surface. |
546
- | `globalMousePos` | `MousePos` | uncontrolled | Externally controlled global pointer position (for coordinating many surfaces). |
547
- | `mouseOffset` | `MousePos` | uncontrolled | Externally controlled pointer offset from the element center. |
548
- | `mouseContainer` | `RefObject<HTMLElement \| null> \| HTMLElement \| null` | `null` (viewport) | Element whose bounds define the pointer-tracking coordinate space. |
549
-
550
- ### `<GlassButton>`
551
-
552
- Extends native `<button>` attributes (`onClick`, `disabled`, `type`, `aria-*`,
553
- `ref`, …) — all are forwarded.
554
-
555
- | Prop | Type | Default | Description |
556
- | --- | --- | --- | --- |
557
- | `variant` | `'primary' \| 'secondary' \| 'subtle'` | `'primary'` | Visual emphasis. |
558
- | `size` | `'sm' \| 'md' \| 'lg' \| 'icon'` | `'md'` | Sizing; `icon` is square for a single-icon child. |
559
- | `asChild` | `boolean` | `false` | Render the single child element instead of a `<button>` (see [`asChild`](#the-aschild-polymorphism-pattern)). |
560
- | `shine` | `boolean` | `true` | Brief highlight sweep on press. |
561
- | `contentClassName` | `string` | — | Class on the isolated content layer (the span holding children). |
562
- | `glassProps` | `Partial<Omit<LiquidGlassProps, 'children'>>` | — | Escape hatch: pass-through overrides to the underlying `<LiquidGlass>`. |
563
-
564
- ### `<GlassCard>`
565
-
566
- Extends native `<div>` attributes (`ref`, `className`, `style`, `children`, …).
567
-
568
- | Prop | Type | Default | Description |
569
- | --- | --- | --- | --- |
570
- | `elevation` | `'flat' \| 'raised' \| 'floating'` | `'raised'` | Ambient lift under the card. |
571
- | `asChild` | `boolean` | `false` | Render the single child element instead of a `<div>`. |
572
- | `contentClassName` | `string` | — | Class on the isolated content layer. |
573
- | `glassProps` | `Partial<Omit<LiquidGlassProps, 'children'>>` | — | Pass-through overrides to the underlying `<LiquidGlass>`. |
574
-
575
- ### `<GlassSegmentedControl>`
576
-
577
- | Prop | Type | Default | Description |
578
- | --- | --- | --- | --- |
579
- | `options` | `GlassSegmentedOption[]` | — | Segments: `{ value, label?, icon?, disabled? }`. Icon-only options are supported. |
580
- | `value` | `string` | — | Controlled selected value. When set, the control is controlled. |
581
- | `defaultValue` | `string` | first option | Initial selected value when uncontrolled. |
582
- | `onValueChange` | `(value: string) => void` | — | Fires on every user selection with the newly-selected value. |
583
- | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant. |
584
- | `label` | `ReactNode` | — | Accessible group label (rendered into the `<legend>`). |
585
- | `aria-label` | `string` | — | Accessible group label as a plain string (alternative to `label`). |
586
- | `showLabel` | `boolean` | `false` | Render the `label` visually above the control instead of hiding it. |
587
- | `name` | `string` | generated id | Stable `name` for the radio group. |
588
- | `className` | `string` | — | Class on the root `<fieldset>`. |
589
- | `style` | `CSSProperties` | — | Inline styles on the root `<fieldset>`. |
590
- | `glassProps` | `Partial<Omit<LiquidGlassProps, 'children'>>` | — | Pass-through overrides to the indicator's `<LiquidGlass>`. |
591
-
592
- ### Also exported
593
-
594
- - **Hooks** — `useGlassCapabilities`, `useReducedMotion`, `useMousePosition`.
595
- - **Utilities** — `detectGlassCapabilities`, `getConservativeGlassCapabilities`,
596
- `getDisplacementMap`, `roundedRectSDF`, `smoothStep`,
597
- `calculateDirectionalScale`, `calculateElasticTranslation`,
598
- `getGlassEdgeShadow`, and the `GLASS_EDGE_LIGHT` / `GLASS_EDGE_DARK` constants.
599
- - **Types** — `LiquidGlassProps`, `DisplacementMode`, `GlassVariant`,
600
- `GlassSize`, `GlassCenter`, `MousePos`, `GlassCapabilities`, `GlassButtonProps`,
601
- `GlassCardProps`, `GlassSegmentedControlProps`, `GlassSegmentedOption`, and the
602
- variant/size/elevation unions. The native barrel additionally exports
603
- `LiquidGlassCoreProps` (the platform-neutral base).
604
- - **`VERSION`** — the package version string.
605
-
606
- ---
607
-
608
- ## Storybook
609
-
610
- A live showcase (refraction, all five modes, the prebuilt components, and the
611
- fallback tiers) runs in Storybook:
612
-
613
- ```bash
614
- pnpm storybook # dev server at http://localhost:6006
615
- pnpm build-storybook # static build into storybook-static/
616
- ```
617
-
618
- The **Showcase** story is the headline demo: full refraction in Chromium, the
619
- frosted fallback in Firefox/Safari.
620
-
621
- ---
622
-
623
- ## License
624
-
625
- MIT — see [`LICENSE`](./LICENSE). The license includes an attribution note
626
- acknowledging the technique's lineage while affirming this is an independent
627
- reimplementation.
20
+ <Glass style={{ borderRadius: 28, padding: 24, background: '#ffffff33' }}
21
+ optics={{ frost: 3, strength: 0.2, dispersion: 0.6 }}>
22
+ A glass surface
23
+ </Glass>
24
+ <GlassButton>Save changes</GlassButton>
25
+ <GlassSwitch ariaLabel="Notifications" defaultChecked />
26
+ <GlassSlider ariaLabel="Volume" defaultValue={50} />
27
+ <GlassSegmentedControl label="Time range" defaultValue="week" options={[
28
+ { value: 'day', label: 'Day' }, { value: 'week', label: 'Week' },
29
+ ]} />
30
+ <div style={{ height: 400 }}>
31
+ <GlassNotification title="Matthew" avatar="MA" body="The glass collection is ready." />
32
+ </div>
33
+ ```
34
+
35
+ React 18+ and React DOM are peers. Import the stylesheet once for the composed
36
+ controls. `Glass` and its alias `LiquidGlass` expose the upstream engine's props.
37
+ Switch, slider and segmented control support controlled state or initial defaults,
38
+ native keyboard controls, form values and disabled states. Switch and slider motion
39
+ honors `prefers-reduced-motion`. Give all controls accessible labels.
40
+
41
+ The switch, slider and notification adapt the MIT upstream examples at revision
42
+ `4e7b769e1df7e5a7d3669fef22417fe3d2f79ade`; attribution is preserved in source
43
+ and `licenses/samasante-liquid-glass-MIT.txt`. The pinned dependency is bundled
44
+ in the web builds to support both ESM and CommonJS without requiring consumers
45
+ to load an ESM-only dependency through `require()`.
46
+
47
+ ## Browser behavior
48
+
49
+ Arbitrary live-backdrop refraction is Chromium-only. Safari and Firefox retain
50
+ frosted glass; use `refract` with a decorative copy for cross-browser bending.
51
+ The notification recipe aligns a wallpaper copy beneath its lens. Keep copies
52
+ free of controls, IDs, data fetching and effects. The upstream video/canvas mode
53
+ uses WebGL. Large or numerous lenses can affect frame rate.
54
+
55
+ ## Migrating from 0.2.x
56
+
57
+ Version 0.3 replaces the previous custom **web** implementation and its API.
58
+ Replace `cornerRadius` and `padding` with CSS in `style`; replace the old optical
59
+ props (`displacement`, `displacementScale`, `blurAmount`, etc.) with upstream
60
+ `optics`. `LiquidGlass` is now an alias for `Glass`, not a legacy-prop adapter.
61
+ The previous web capability, displacement, backdrop-sampling and motion helper
62
+ exports have been removed. Button/card/segmented-control support the props shown
63
+ in the new Storybook; consult the generated types for the complete new API.
64
+ Old docs and plans under `docs/` describe 0.2.x and remain historical references.
65
+
66
+ ESM, CommonJS and `styles.css` package paths remain available. The separate
67
+ `react-native` export condition still resolves to the previous native frosted
68
+ primitive backed by Expo Blur and Linear Gradient. Its existing props remain
69
+ unchanged; the new web-only composed controls are not exported on native.
70
+
71
+ ## Development
72
+
73
+ ```sh
74
+ corepack pnpm install
75
+ corepack pnpm lint
76
+ corepack pnpm typecheck
77
+ corepack pnpm test
78
+ corepack pnpm test:native
79
+ corepack pnpm build
80
+ corepack pnpm build-storybook
81
+ corepack pnpm exec playwright test
82
+ ```
83
+
84
+ Storybook contains examples for the engine and all six composed controls.
85
+ `test/consumer-fixture` checks ESM/CommonJS exports after a build.