{"_id":"@aberhamm/liquid-glass-react","_rev":"3-0052d612af8aba06d6bd3955d5f5f0e7","name":"@aberhamm/liquid-glass-react","dist-tags":{"latest":"0.2.1"},"versions":{"0.2.0":{"name":"@aberhamm/liquid-glass-react","version":"0.2.0","keywords":["react","liquid-glass","glassmorphism","backdrop-filter","svg-filter","displacement-map","glass","ui","components","refraction"],"author":{"name":"Matthew Aberham"},"license":"MIT","_id":"@aberhamm/liquid-glass-react@0.2.0","maintainers":[{"name":"aberhamm","email":"matthew.aberham@gmail.com"}],"homepage":"https://github.com/aberhamm/liquid-glass-react#readme","bugs":{"url":"https://github.com/aberhamm/liquid-glass-react/issues"},"dist":{"shasum":"66b51dd886eaab9aede8dfb0affe8171621c91e0","tarball":"https://registry.npmjs.org/@aberhamm/liquid-glass-react/-/liquid-glass-react-0.2.0.tgz","fileCount":16,"integrity":"sha512-V4OWRucDKXkdFuQl942IaQofs4/INAVRp/FRX1rZhwmqc+vh9OarG6MSAkEGaA31C/1WI4IPj1oi4hpSYKFXXg==","signatures":[{"sig":"MEYCIQCP1J3tmCqR0XvNwD2uST2h/abJRi4lEGQFShu0PEtjxgIhANEeox5faokiXatCbX9GaUSuYCBlOdKjacnadU7hJ12d","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":279085},"main":"./dist/index.cjs","pnpm":{"onlyBuiltDependencies":["@biomejs/biome","esbuild"]},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"},"react-native":{"types":"./dist/native/index.native.d.ts","default":"./dist/native/index.native.js"}},"./styles.css":"./dist/liquid-glass-react.css"},"gitHead":"9f620c15c2602e5d1b3775d6e8f0bc2418604282","scripts":{"e2e":"pnpm build-storybook && playwright test","attw":"attw --pack . --exclude-entrypoints ./styles.css","lint":"biome check .","test":"vitest run","build":"vite build && pnpm build:native","e2e:ui":"pnpm build-storybook && playwright test --ui","format":"biome format --write .","storybook":"storybook dev -p 6006 --no-open","typecheck":"tsc --noEmit","pack:check":"npm pack --dry-run","test:watch":"vitest","test:native":"vitest run --config vitest.native.config.ts","verify:expo":"node scripts/verify-native-resolution.mjs && vitest run --config vitest.native.config.ts","build:native":"tsc -p tsconfig.native.build.json","prepublishOnly":"pnpm lint && pnpm typecheck && pnpm test && pnpm build","build-storybook":"storybook build","typecheck:native":"tsc -p tsconfig.native.json --noEmit"},"_npmUser":{"name":"aberhamm","email":"matthew.aberham@gmail.com"},"repository":{"url":"git+https://github.com/aberhamm/liquid-glass-react.git","type":"git"},"_npmVersion":"11.13.0","description":"An independent React reimplementation of a liquid-glass UI component library.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.16.0","react-native":"./dist/native/index.native.js","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.3","devDependencies":{"vite":"^6.0.7","jsdom":"^25.0.1","react":"^18.3.1","vitest":"^2.1.8","expo-blur":"^56.0.3","react-dom":"^18.3.1","storybook":"^8.6.18","typescript":"^5.7.3","http-server":"^14.1.1","@types/react":"^18.3.18","react-native":"^0.83.10","@biomejs/biome":"^1.9.4","vite-plugin-dts":"^4.4.0","@playwright/test":"^1.61.0","@storybook/react":"^8.6.18","@types/react-dom":"^18.3.5","react-test-renderer":"^18.3.1","@testing-library/dom":"^10.4.0","expo-linear-gradient":"^56.0.4","@arethetypeswrong/cli":"^0.18.3","@storybook/react-vite":"^8.6.18","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@storybook/addon-essentials":"^8.6.14","@testing-library/react-native":"^14.0.1"},"peerDependencies":{"react":">=18","expo-blur":">=13","react-dom":">=18","react-native":">=0.74","expo-linear-gradient":">=13"},"peerDependenciesMeta":{"expo-blur":{"optional":true},"react-native":{"optional":true},"expo-linear-gradient":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/liquid-glass-react_0.2.0_1783319823204_0.9438675645527017","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aberhamm/liquid-glass-react","version":"0.2.1","keywords":["react","liquid-glass","glassmorphism","backdrop-filter","svg-filter","displacement-map","glass","ui","components","refraction"],"author":{"name":"Matthew Aberham"},"license":"MIT","_id":"@aberhamm/liquid-glass-react@0.2.1","maintainers":[{"name":"aberhamm","email":"matthew.aberham@gmail.com"}],"homepage":"https://github.com/aberhamm/liquid-glass-react#readme","bugs":{"url":"https://github.com/aberhamm/liquid-glass-react/issues"},"dist":{"shasum":"d12c30d4afac86d2156e5e19c4c424a6b66835ab","tarball":"https://registry.npmjs.org/@aberhamm/liquid-glass-react/-/liquid-glass-react-0.2.1.tgz","fileCount":16,"integrity":"sha512-vVed3+j1epoY9Ta8TGtfty5ngm70IaFgSqZSPahfoABjC+ph+ZWnnio6rVEI+a7w1hyI3rMb2lMMOlo8PsoA0A==","signatures":[{"sig":"MEYCIQCurZH4AxfXeTHoKRiNsXMzpKUmt6UxH8M9G3sx9q6SnQIhAJvjj+4DwTiIjn5/9MzXpdhNo7STJjp3KWKa4s4AVexE","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":279688},"main":"./dist/index.cjs","pnpm":{"onlyBuiltDependencies":["@biomejs/biome","esbuild"]},"type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"},"react-native":{"types":"./dist/native/index.native.d.ts","default":"./dist/native/index.native.js"}},"./styles.css":"./dist/liquid-glass-react.css"},"gitHead":"0e8da46da1da35055d60a027faea8e4cfae91088","scripts":{"e2e":"pnpm build-storybook && playwright test","attw":"attw --pack . --exclude-entrypoints ./styles.css","lint":"biome check .","test":"vitest run","build":"vite build && pnpm build:native","e2e:ui":"pnpm build-storybook && playwright test --ui","format":"biome format --write .","storybook":"storybook dev -p 6006 --no-open","typecheck":"tsc --noEmit","pack:check":"npm pack --dry-run","test:watch":"vitest","test:native":"vitest run --config vitest.native.config.ts","verify:expo":"node scripts/verify-native-resolution.mjs && vitest run --config vitest.native.config.ts","build:native":"tsc -p tsconfig.native.build.json","prepublishOnly":"pnpm lint && pnpm typecheck && pnpm test && pnpm build","build-storybook":"storybook build","typecheck:native":"tsc -p tsconfig.native.json --noEmit"},"_npmUser":{"name":"aberhamm","email":"matthew.aberham@gmail.com"},"repository":{"url":"git+https://github.com/aberhamm/liquid-glass-react.git","type":"git"},"_npmVersion":"11.17.0","description":"An independent React reimplementation of a liquid-glass UI component library.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.16.0","react-native":"./dist/native/index.native.js","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.3","devDependencies":{"vite":"^6.0.7","jsdom":"^25.0.1","react":"^18.3.1","vitest":"^2.1.8","expo-blur":"^56.0.3","react-dom":"^18.3.1","storybook":"^8.6.18","typescript":"^5.7.3","http-server":"^14.1.1","@types/react":"^18.3.18","react-native":"^0.83.10","@biomejs/biome":"^1.9.4","vite-plugin-dts":"^4.4.0","@playwright/test":"^1.61.0","@storybook/react":"^8.6.18","@types/react-dom":"^18.3.5","react-test-renderer":"^18.3.1","@testing-library/dom":"^10.4.0","expo-linear-gradient":"^56.0.4","@arethetypeswrong/cli":"^0.18.3","@storybook/react-vite":"^8.6.18","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@storybook/addon-essentials":"^8.6.14","@testing-library/react-native":"^14.0.1"},"peerDependencies":{"react":">=18","expo-blur":">=13","react-dom":">=18","react-native":">=0.74","expo-linear-gradient":">=13"},"peerDependenciesMeta":{"expo-blur":{"optional":true},"react-native":{"optional":true},"expo-linear-gradient":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/liquid-glass-react_0.2.1_1783769526009_0.9586385162275524","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-07-06T06:37:02.990Z","modified":"2026-09-07T12:13:35.545Z","0.2.0":"2026-07-06T06:37:03.344Z","0.2.1":"2026-07-11T11:32:06.222Z"},"bugs":{"url":"https://github.com/aberhamm/liquid-glass-react/issues"},"author":{"name":"Matthew Aberham"},"license":"MIT","homepage":"https://github.com/aberhamm/liquid-glass-react#readme","keywords":["react","liquid-glass","glassmorphism","backdrop-filter","svg-filter","displacement-map","glass","ui","components","refraction"],"repository":{"url":"git+https://github.com/aberhamm/liquid-glass-react.git","type":"git"},"description":"An independent React reimplementation of a liquid-glass UI component library.","maintainers":[{"email":"matthew.aberham@gmail.com","name":"matthewforreal"}],"readme":"# @aberhamm/liquid-glass-react\n\nLiquid-glass UI primitives and batteries-included components for React — a\nfrosted, refractive \"glass\" surface driven by `backdrop-filter` and SVG\ndisplacement filters, with graceful per-engine degradation and zero runtime\ndependencies.\n\n> **Independent reimplementation, not a fork.** This library reimplements the\n> liquid-glass technique popularized by\n> [`rdev/liquid-glass-react`](https://github.com/rdev/liquid-glass-react) and\n> [`shuding/liquid-glass`](https://github.com/shuding/liquid-glass). It shares\n> the *prop surface and intent* — not the code. No source was copied; see\n> [`LICENSE`](./LICENSE) and [`docs/PARITY.md`](./docs/PARITY.md).\n\n- **Frosted glass by default, everywhere** — a portable `backdrop-filter` surface\n  (blur + saturate + inset-shadow glass edge + rim + elastic motion) that renders\n  the same in Chrome, Safari/WebKit, and Firefox. This is the default look.\n- **Opt-in live-backdrop refraction (`displacement`)** — Chromium-only SVG\n  `feDisplacementMap` composited over the live backdrop via `backdrop-filter:\n  url()`, plus chromatic aberration. Silently falls back to frosted on\n  Firefox/Safari.\n- **Opt-in cross-browser refraction (`refract` / `size` / `center`)** — applies\n  the same SVG displacement as an element `filter: url()` on a *copy* of content,\n  so a real refractive lens renders in Chrome, Safari **and** Firefox.\n- **Web + Expo/React Native from one install** — the same `<LiquidGlass>` import\n  resolves to a native (`expo-blur`) frosted build under Metro. iOS is the quality\n  bar; Android is best-effort.\n- **Identical box geometry across every tier** — degrading never shifts layout.\n- **SSR-safe** — conservative capabilities until mount, no hydration mismatch.\n- **Zero runtime dependencies.** React 18+ is a peer dependency; the Expo peer\n  deps are optional and web consumers never install them.\n\n---\n\n## Install\n\n```bash\npnpm add @aberhamm/liquid-glass-react\n# or\nnpm install @aberhamm/liquid-glass-react\n# or\nyarn add @aberhamm/liquid-glass-react\n```\n\n`react` and `react-dom` `>=18` are **peer dependencies** — install them in your\napp if you haven't already:\n\n```bash\npnpm add react react-dom\n```\n\n### CSS import\n\nThe **prebuilt components** (`GlassButton`, `GlassCard`,\n`GlassSegmentedControl`) require their stylesheet, imported **once** anywhere in\nyour app (e.g. your root layout or entry):\n\n```ts\nimport '@aberhamm/liquid-glass-react/styles.css';\n```\n\nThe low-level `<LiquidGlass>` **primitive does not need the stylesheet** — it is\nfully self-contained and works without it.\n\n---\n\n## Quick start\n\n### `LiquidGlass` (the primitive)\n\n```tsx\nimport { LiquidGlass } from '@aberhamm/liquid-glass-react';\n\nexport function Badge() {\n  return (\n    <LiquidGlass cornerRadius={24} padding=\"16px 24px\">\n      <span style={{ color: 'white', fontWeight: 600 }}>Liquid glass</span>\n    </LiquidGlass>\n  );\n}\n```\n\n### `GlassButton`\n\n```tsx\nimport { GlassButton } from '@aberhamm/liquid-glass-react';\nimport '@aberhamm/liquid-glass-react/styles.css';\n\nexport function Actions() {\n  return (\n    <GlassButton variant=\"primary\" size=\"md\" onClick={() => alert('clicked')}>\n      Get started\n    </GlassButton>\n  );\n}\n```\n\n### `GlassCard`\n\n```tsx\nimport { GlassCard } from '@aberhamm/liquid-glass-react';\nimport '@aberhamm/liquid-glass-react/styles.css';\n\nexport function Panel() {\n  return (\n    <GlassCard elevation=\"floating\">\n      <h3>Frosted panel</h3>\n      <p>Content stays legible over busy backgrounds.</p>\n    </GlassCard>\n  );\n}\n```\n\n### `GlassSegmentedControl`\n\n```tsx\nimport { useState } from 'react';\nimport { GlassSegmentedControl } from '@aberhamm/liquid-glass-react';\nimport '@aberhamm/liquid-glass-react/styles.css';\n\nexport function ViewToggle() {\n  const [value, setValue] = useState('grid');\n  return (\n    <GlassSegmentedControl\n      label=\"View mode\"\n      value={value}\n      onValueChange={setValue}\n      options={[\n        { value: 'grid', label: 'Grid' },\n        { value: 'list', label: 'List' },\n        { value: 'map', label: 'Map' },\n      ]}\n    />\n  );\n}\n```\n\nThe control is a **native radiogroup** (`<fieldset>` + visually-hidden\n`<input type=\"radio\">`), so Arrow/Home/End/Space keyboard navigation and\nscreen-reader semantics work for free. Pass `label` (or `aria-label`) for the\naccessible group name; set `showLabel` to render it visually.\n\n---\n\n## Web usage: frosted default, opt-in refraction\n\nOn the web the glass renders a **portable frosted surface by default** — the same\n`backdrop-filter` blur/saturate look in Chrome, Safari/WebKit, and Firefox. No\nprops are required:\n\n```tsx\nimport { LiquidGlass } from '@aberhamm/liquid-glass-react';\n\n<LiquidGlass cornerRadius={24} padding=\"16px 24px\">\n  <span>Frosted by default — portable across engines</span>\n</LiquidGlass>;\n```\n\nThere are **two independent, opt-in ways** to add real refraction. They compose,\nand both are off by default.\n\n### `displacement` — bend the LIVE page (Chromium only)\n\n`displacement` attaches the SVG `feDisplacementMap` as a `backdrop-filter: url()`,\nso it warps the **live content behind** the glass. This only composites in\nChromium (Chrome, Edge, Brave, Opera); Firefox and Safari/WebKit **silently fall\nback** to the frosted surface.\n\n```tsx\n// Chromium bends the real page behind the glass; other engines stay frosted.\n<LiquidGlass displacement>\n  <span>Live-backdrop refraction where supported</span>\n</LiquidGlass>;\n```\n\n> **Upgrading from `0.1.x`?** Refraction used to be the default look. Add\n> `displacement` to restore the old Chromium live-backdrop bend.\n\n### `refract` / `size` / `center` — refract a COPY (cross-browser)\n\nThe cross-browser path applies the same SVG displacement as an **element**\n`filter: url()` on a **copy** of content. Because it filters an element (not the\nbackdrop), a real refractive lens renders in Chrome, Safari **and** Firefox. Two\nmodes:\n\n- **In-place** — pass `size` and/or `center` to refract a copy of the glass's own\n  children, with the crisp children on top:\n\n  ```tsx\n  <LiquidGlass size={{ width: 320, height: 200 }} center={{ x: 0.5, y: 0.5 }}>\n    <span>Refracts a copy of its own children — all engines</span>\n  </LiquidGlass>;\n  ```\n\n- **Lens over foreign content** — pass `refract={node}` to float a lens over\n  content the glass doesn't own; `behind` fills any bleed the copied node doesn't\n  cover:\n\n  ```tsx\n  <LiquidGlass refract={<img src=\"/poster.jpg\" alt=\"\" />} behind=\"#0b0b0f\">\n    <span>Floats a refractive lens over the poster</span>\n  </LiquidGlass>;\n  ```\n\n> **Which bends what?** `displacement` bends the **live page** behind the glass\n> (Chromium only). `refract` / `size` / `center` refract a **copy** of content via\n> an element filter (Chrome, Safari, **and** Firefox). They are distinct and\n> compose. The refracted copy is decorative — `aria-hidden` + `inert` +\n> `pointer-events: none` — so keep the copied subtree **purely presentational**\n> (no `id`s, effects, focusable controls, or data fetching; it mounts twice).\n\n---\n\n## Expo / React Native\n\nThe **same `<LiquidGlass>` import** works in an Expo / React Native app. Metro\nresolves the native build automatically via the package's `react-native` export\ncondition — there is no separate import path:\n\n```tsx\nimport { LiquidGlass } from '@aberhamm/liquid-glass-react';\nimport { Text } from 'react-native';\n\nexport function Badge() {\n  return (\n    <LiquidGlass cornerRadius={24} padding={16} onPress={() => {}}>\n      <Text style={{ color: 'white', fontWeight: '600' }}>Liquid glass</Text>\n    </LiquidGlass>\n  );\n}\n```\n\nThe native surface renders **frosted glass** — an `expo-blur` `BlurView` with a\ntint and a gradient sheen. It is a genuinely different renderer from the web build,\nso the web-only props are **documented no-ops** on native: `displacement`,\n`refract`, `size`, `center`, `behind`, `saturation`, `variant`, `mode`,\n`aberrationIntensity`, and the pointer/motion props. The native primitive uses\n`onPress` (the web primitive uses `onClick`) and takes `style?: ViewStyle`.\n\n> **iOS is the quality bar; Android is best-effort.** Android uses expo-blur's\n> `experimentalBlurMethod` and degrades gracefully where blur support is limited.\n\n### Optional peer deps\n\nThe Expo target needs two **optional** peer dependencies — `expo-blur` and\n`expo-linear-gradient` — plus `react-native` (and `expo` itself), which your Expo\napp already provides. They are marked `optional` in `peerDependenciesMeta`, so\n**web-only consumers never install them**:\n\n```bash\nnpx expo install expo-blur expo-linear-gradient\n# react-native and expo come from your Expo app itself\n```\n\nThe prebuilt `GlassButton`, `GlassCard`, and `GlassSegmentedControl` are\n**web-only this round** — they are DOM components and are intentionally **not**\npart of the native barrel (`src/index.native.ts`), so they won't resolve in a\nnative bundle. Use the `<LiquidGlass>` primitive on native.\n\n---\n\n## What works where\n\n| Capability | Chrome / Edge | Safari / WebKit | Firefox | Expo (iOS) | Expo (Android) |\n| --- | --- | --- | --- | --- | --- |\n| **Frosted glass** (default) | ✅ | ✅ | ✅ | ✅ | ⚠️ best-effort |\n| **`displacement`** (live-backdrop bend) | ✅ | frosted fallback | frosted fallback | no-op | no-op |\n| **Cross-browser refraction** (`refract` / `size` / `center`) | ✅ | ✅ | ✅ | no-op | no-op |\n| **Prebuilt** `GlassButton` / `GlassCard` / `GlassSegmentedControl` | ✅ | ✅ | ✅ | ❌ web-only | ❌ web-only |\n\nFrosted is the portable default everywhere. `displacement` bends the live page and\nis Chromium-only (frosted fallback elsewhere). The cross-browser `refract` path\nrenders a refractive copy in all three web engines. The prebuilt components are\nweb-only this round.\n\n---\n\n## The `asChild` polymorphism pattern\n\n`GlassButton` and `GlassCard` accept `asChild`. When set, the component renders\n**your** single child element instead of its default tag (`<button>` / `<div>`),\nmerging its props, `className`, and `ref` onto it. The child then owns its own\nsemantics and accessibility — ideal for link-styled buttons:\n\n```tsx\nimport { GlassButton } from '@aberhamm/liquid-glass-react';\n\n<GlassButton asChild variant=\"secondary\">\n  <a href=\"/docs\">Read the docs</a>\n</GlassButton>;\n```\n\n---\n\n## Capability detection\n\nUse the hook (or the underlying detector) to branch on what the current engine\ncan actually render. The single gate is `canRefract` — `true` only when the full\nSVG-displacement refraction will composite (Chromium with `backdrop-filter`):\n\n```tsx\nimport { useGlassCapabilities } from '@aberhamm/liquid-glass-react';\n\nfunction Hint() {\n  const caps = useGlassCapabilities();\n  return caps.canRefract\n    ? <p>Full refraction is active.</p>\n    : <p>Showing the frosted fallback for this browser.</p>;\n}\n```\n\nDuring SSR and before the mount effect runs, every capability is conservatively\n`false` (so the server and first client render agree). For one-shot,\nnon-reactive checks there is also `detectGlassCapabilities()`:\n\n```ts\nimport { detectGlassCapabilities } from '@aberhamm/liquid-glass-react';\n\nconst caps = detectGlassCapabilities(); // GlassCapabilities\nif (caps.canRefract) {\n  /* ... */\n}\n```\n\n`GlassCapabilities` fields: `supportsBackdropFilter`, `isChromium`,\n`supportsSvgBackdropDisplacement`, `isFirefox`, `prefersReducedMotion`, and the\nderived `canRefract`.\n\n---\n\n## Browser support\n\nThe **default** render is the portable frosted surface — identical in Chrome,\nSafari/WebKit, and Firefox. The runtime `canRefract` gate\n(`supportsBackdropFilter && supportsSvgBackdropDisplacement`, where the latter is\npositive Blink-family detection) only decides what the **opt-in `displacement`**\nprop does: whether the live-backdrop SVG bend composites, or degrades to frosted.\nThe cross-browser `refract` / `size` / `center` path does **not** depend on this\ngate — it renders in every engine. All tiers share **identical box geometry**, so\nmoving between them causes **no layout shift**. See\n[`docs/PARITY.md`](./docs/PARITY.md) for the full contract.\n\nBehavior when `displacement` is requested:\n\n| Engine | `canRefract` | With `displacement` | Default (no `displacement`) |\n| --- | --- | --- | --- |\n| **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. |\n| **Firefox** (Gecko) | `false` | **Frosted fallback** — `displacement` degrades silently; no live-backdrop bend. | Frosted surface. |\n| **Safari / WebKit** | `false` | **Frosted fallback** — same as Firefox; WebKit does not composite the SVG displacement over the backdrop. | Frosted surface. |\n| 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. |\n| **SSR / pre-mount** | `false` | **Conservative (frosted/degraded)** — all capabilities `false` until the client mount effect re-evaluates, avoiding hydration mismatch. | Conservative. |\n\nThe cross-browser `refract` / `size` / `center` path renders a refractive **copy**\nin all of the above web engines regardless of `canRefract` (see\n[Web usage](#refract--size--center--refract-a-copy-cross-browser)).\n\nWhy not feature-detect refraction directly? There is no standardized\n`CSS.supports` probe for \"an SVG `feDisplacementMap` composites correctly over\n`backdrop-filter`\" — it's a Chromium rendering-pipeline quirk. The library uses\npositive Blink detection (a pragmatic, revisitable heuristic) to keep Safari and\nFirefox in the frosted tier by construction. Details and caveats live in\n[`docs/PARITY.md`](./docs/PARITY.md).\n\n---\n\n## Displacement `mode`\n\nThe primitive's `mode` prop selects the displacement algorithm used to generate\nthe SVG filter (full effect renders in Chromium; other engines show the frosted\nfallback regardless of `mode`):\n\n| Mode | Look |\n| --- | --- |\n| `standard` *(default)* | Balanced, edge-weighted displacement — the default glass look. |\n| `polar` | Radial/polar displacement, stronger toward the perimeter. |\n| `prominent` | Exaggerated displacement for a heavier \"thick glass\" feel. |\n| `shader` | Shader-style profile for sharper highlights. The displacement map is **generated at runtime** (canvas → `data:` URL feeding an `feImage`). |\n| `turbulence` | Adds fractal `feTurbulence` for an organic, watery, **procedural frosted-ripple** distortion. |\n\n```tsx\n<LiquidGlass mode=\"turbulence\">…</LiquidGlass>\n```\n\n---\n\n## Content-adaptive auto-tint (`adaptiveTint`)\n\nApple's Liquid Glass adapts its tint and content treatment to the brightness of\nwhatever is behind it. `adaptiveTint` brings that to `<LiquidGlass>`: when `true`\nthe glass samples the luminance of its backdrop and automatically shifts toward a\n**light** or **dark** treatment — the SAME tint / displacement / blur plumbing\n`overLight` already drives — so foreground content (labels, captions) stays\nlegible without hand-tuning. A bright backdrop yields the light treatment with\ndark ink; a dark backdrop yields the default treatment with light ink.\n\n```tsx\n// No overLight needed — the glass figures out light vs dark for you.\n<LiquidGlass adaptiveTint>\n  <span>Adapts to its backdrop</span>\n</LiquidGlass>\n```\n\nIt is **additive and opt-in**: `adaptiveTint` defaults to `false`, so the default\nrender is byte-for-byte unchanged and the luminance sampler is never loaded on the\ndefault path.\n\n**Precedence — `overLight` always wins.** `overLight` is the manual override;\n`adaptiveTint` is the auto path. They never fight. When `overLight` is set\nexplicitly it short-circuits the auto path:\n\n```ts\neffectiveOverLight = overLight ?? (adaptiveTint && scheme ? scheme === 'light' : false)\n```\n\n**SSR / hydration-safe.** The server and the first client paint render the\ndefault (unsampled) treatment, so hydration never mismatches; the sampled\ntreatment is applied in an effect after mount.\n\n**Graceful degradation.** When the backdrop can't be sampled — a **cross-origin**\nbackdrop taints the canvas, there's no canvas, or it's SSR — the reading is\n`sampled: false` and auto-tint silently falls back to `overLight ?? false`. No\nerror, no flicker loop, no `console.error`.\n\n**Accessibility.** Under `(prefers-contrast: more)` the increased-contrast\ntreatment **wins**: auto-tint never undercuts the high-contrast surface or\nlegibility treatment.\n\n> ⚠️ **Limitation.** Auto-tint is **best-effort legibility**. Critical text over\n> unknown or cross-origin backdrops (which cannot be sampled) should be verified\n> manually — the auto path falls back rather than guessing.\n\n---\n\n## Scroll-aware shadow (`scrollAwareShadow`)\n\nApple's Liquid Glass deepens a pinned bar's drop-shadow as content scrolls\nbeneath it — lifting it above the text — and eases it over solid backgrounds.\n`scrollAwareShadow` brings that to `<LiquidGlass>`: when `true` the glass reuses\nthe **same** backdrop-luminance sampler as `adaptiveTint` and modulates **only**\nits decoupled drop-shadow — **deeper and darker** over a dark/dense backdrop,\n**shallower and lighter** over a light/solid one.\n\n```tsx\n// The drop-shadow tracks the backdrop as content scrolls beneath the bar.\n<LiquidGlass scrollAwareShadow>\n  <span>Pinned toolbar</span>\n</LiquidGlass>\n```\n\nIt is **additive and opt-in**: `scrollAwareShadow` defaults to `false`, so the\ndefault render is byte-for-byte unchanged and the sampler is never loaded on the\ndefault path. Only the shadow's **blur / offset / opacity** vary — it stays the\ndecoupled sibling behind the clipped surface (never a `box-shadow` on the clipped\nnode), so no clipping eats it and no new layer is added.\n\n**SSR / hydration-safe.** The server and the first client paint render the\nconservative static shadow; the modulated shadow is applied in an effect after\nmount, so hydration never mismatches.\n\n**Graceful degradation.** When the backdrop can't be sampled (cross-origin taint,\nno canvas, SSR) the reading is `sampled: false` and the shadow falls back to the\nstatic one — no error, no flicker loop, no `console.error`.\n\n**Reduced motion.** Under `(prefers-reduced-motion: reduce)` the shadow **snaps**\nbetween depths instead of animating (the `box-shadow` transition is dropped).\n\n---\n\n## Material variants: Regular vs Clear (`variant`)\n\nApple distinguishes two Liquid Glass materials, and `variant` lets you pick one\nintentionally instead of hand-tuning opacity:\n\n- **`regular`** (default) — the dependable, fully adaptive control surface. This\n  is **exactly** today's behavior, including content-adaptive auto-tint when\n  [`adaptiveTint`](#content-adaptive-auto-tint-adaptivetint) is on.\n- **`clear`** — a permanently **more transparent** material for media-rich\n  contexts (floating over a photo or video). It is **non-adaptive by\n  definition**, with a subtle dimming scrim behind the content so labels stay\n  legible over busy media.\n\n```tsx\n// Dependable control surface (default).\n<LiquidGlass variant=\"regular\">\n  <span>Toolbar</span>\n</LiquidGlass>\n\n// Maximally transparent over media — clearer, with a legibility scrim.\n<LiquidGlass variant=\"clear\">\n  <span>Over a photo</span>\n</LiquidGlass>\n```\n\nIt is **additive and non-breaking**: `variant` defaults to `'regular'`, so\nomitting it keeps today's render byte-for-byte unchanged. It is a small parameter\nlookup feeding the existing surface/content styles — not a theming system.\n\n**Don't mix — Clear is non-adaptive.** Per Apple's guidance the two should never\nbe mixed in the same context. In `'clear'`:\n\n- **`adaptiveTint` is a no-op** — Clear never samples the backdrop or flips its\n  tint/ink. (Use `'regular'` + `adaptiveTint` for the adaptive material.)\n- **`overLight` still nudges legibility**, but does NOT re-enable adaptivity.\n\n**Accessibility wins in both variants.** Under `(prefers-contrast: more)` the\nincreased-contrast treatment (solid border, opaque fill, pinned saturation)\napplies to Clear too — a11y beats the Clear aesthetic.\n\nThe prebuilt components (`GlassButton`, `GlassCard`, `GlassSegmentedControl`)\ntake `variant` through their `glassProps` escape hatch — no new per-component\nprop:\n\n```tsx\n<GlassButton glassProps={{ variant: 'clear' }}>Over media</GlassButton>\n```\n\n---\n\n## API reference\n\n### `<LiquidGlass>` (primitive)\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `children` | `ReactNode` | — | Content rendered inside the glass surface. |\n| `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. |\n| `displacementScale` | `number` | `70` | Strength of the refraction distortion; higher bends the backdrop more. |\n| `blurAmount` | `number` | `0.0625` | Backdrop blur radius (px) applied behind the glass. |\n| `saturation` | `number` | `140` | Backdrop saturation multiplier (`1` = unchanged). |\n| `aberrationIntensity` | `number` | `2` | Chromatic aberration (RGB separation) at refracted edges; `0` disables. |\n| `elasticity` | `number` | `0.15` | Pointer-follow softness; `0` is rigid, higher is rubbery. |\n| `cornerRadius` | `number \\| string` | `999` | Corner radius. Number = px; string = CSS length (e.g. `'1rem'`, `'50%'`). |\n| `padding` | `number \\| string` | `'24px 32px'` | Inner padding. Number = px; string = CSS shorthand. |\n| `overLight` | `boolean` | `false` | Hint that the glass sits over a light background; tunes tint/contrast. The manual override — always wins over `adaptiveTint`. |\n| `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)). |\n| `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)). |\n| `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)). |\n| `mode` | `DisplacementMode` | `'standard'` | Displacement algorithm (see [Displacement `mode`](#displacement-mode)). |\n| `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. |\n| `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. |\n| `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. |\n| `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. |\n| `className` | `string` | — | Class name(s) on the outermost glass element. |\n| `style` | `CSSProperties` | — | Inline styles merged onto the outermost element. |\n| `onClick` | `MouseEventHandler<HTMLDivElement>` | — | Click handler forwarded to the surface. |\n| `globalMousePos` | `MousePos` | uncontrolled | Externally controlled global pointer position (for coordinating many surfaces). |\n| `mouseOffset` | `MousePos` | uncontrolled | Externally controlled pointer offset from the element center. |\n| `mouseContainer` | `RefObject<HTMLElement \\| null> \\| HTMLElement \\| null` | `null` (viewport) | Element whose bounds define the pointer-tracking coordinate space. |\n\n### `<GlassButton>`\n\nExtends native `<button>` attributes (`onClick`, `disabled`, `type`, `aria-*`,\n`ref`, …) — all are forwarded.\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `variant` | `'primary' \\| 'secondary' \\| 'subtle'` | `'primary'` | Visual emphasis. |\n| `size` | `'sm' \\| 'md' \\| 'lg' \\| 'icon'` | `'md'` | Sizing; `icon` is square for a single-icon child. |\n| `asChild` | `boolean` | `false` | Render the single child element instead of a `<button>` (see [`asChild`](#the-aschild-polymorphism-pattern)). |\n| `shine` | `boolean` | `true` | Brief highlight sweep on press. |\n| `contentClassName` | `string` | — | Class on the isolated content layer (the span holding children). |\n| `glassProps` | `Partial<Omit<LiquidGlassProps, 'children'>>` | — | Escape hatch: pass-through overrides to the underlying `<LiquidGlass>`. |\n\n### `<GlassCard>`\n\nExtends native `<div>` attributes (`ref`, `className`, `style`, `children`, …).\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `elevation` | `'flat' \\| 'raised' \\| 'floating'` | `'raised'` | Ambient lift under the card. |\n| `asChild` | `boolean` | `false` | Render the single child element instead of a `<div>`. |\n| `contentClassName` | `string` | — | Class on the isolated content layer. |\n| `glassProps` | `Partial<Omit<LiquidGlassProps, 'children'>>` | — | Pass-through overrides to the underlying `<LiquidGlass>`. |\n\n### `<GlassSegmentedControl>`\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `options` | `GlassSegmentedOption[]` | — | Segments: `{ value, label?, icon?, disabled? }`. Icon-only options are supported. |\n| `value` | `string` | — | Controlled selected value. When set, the control is controlled. |\n| `defaultValue` | `string` | first option | Initial selected value when uncontrolled. |\n| `onValueChange` | `(value: string) => void` | — | Fires on every user selection with the newly-selected value. |\n| `size` | `'sm' \\| 'md' \\| 'lg'` | `'md'` | Size variant. |\n| `label` | `ReactNode` | — | Accessible group label (rendered into the `<legend>`). |\n| `aria-label` | `string` | — | Accessible group label as a plain string (alternative to `label`). |\n| `showLabel` | `boolean` | `false` | Render the `label` visually above the control instead of hiding it. |\n| `name` | `string` | generated id | Stable `name` for the radio group. |\n| `className` | `string` | — | Class on the root `<fieldset>`. |\n| `style` | `CSSProperties` | — | Inline styles on the root `<fieldset>`. |\n| `glassProps` | `Partial<Omit<LiquidGlassProps, 'children'>>` | — | Pass-through overrides to the indicator's `<LiquidGlass>`. |\n\n### Also exported\n\n- **Hooks** — `useGlassCapabilities`, `useReducedMotion`, `useMousePosition`.\n- **Utilities** — `detectGlassCapabilities`, `getConservativeGlassCapabilities`,\n  `getDisplacementMap`, `roundedRectSDF`, `smoothStep`,\n  `calculateDirectionalScale`, `calculateElasticTranslation`,\n  `getGlassEdgeShadow`, and the `GLASS_EDGE_LIGHT` / `GLASS_EDGE_DARK` constants.\n- **Types** — `LiquidGlassProps`, `DisplacementMode`, `GlassVariant`,\n  `GlassSize`, `GlassCenter`, `MousePos`, `GlassCapabilities`, `GlassButtonProps`,\n  `GlassCardProps`, `GlassSegmentedControlProps`, `GlassSegmentedOption`, and the\n  variant/size/elevation unions. The native barrel additionally exports\n  `LiquidGlassCoreProps` (the platform-neutral base).\n- **`VERSION`** — the package version string.\n\n---\n\n## Storybook\n\nA live showcase (refraction, all five modes, the prebuilt components, and the\nfallback tiers) runs in Storybook:\n\n```bash\npnpm storybook          # dev server at http://localhost:6006\npnpm build-storybook    # static build into storybook-static/\n```\n\nThe **Showcase** story is the headline demo: full refraction in Chromium, the\nfrosted fallback in Firefox/Safari.\n\n---\n\n## License\n\nMIT — see [`LICENSE`](./LICENSE). The license includes an attribution note\nacknowledging the technique's lineage while affirming this is an independent\nreimplementation.\n","readmeFilename":"README.md"}