ios-liquid-glass-style
v0.1.0
Published
Apple-style iOS Liquid Glass UI component library for React — real backdrop refraction, TypeScript, tree-shakeable, ESM + CJS.
Downloads
157
Maintainers
Readme
ios-liquid-glass-style
Apple-style Liquid Glass UI components for React. A translucent material that visibly bends the content behind it at the rim — with a faint prism fringe — while the interior stays legible. Real backdrop refraction in Chromium (Chrome, Edge, Arc, Brave); an automatic frosted-blur fallback everywhere else.
This is a faithful React port of the original liquid-glass
vanilla implementation — the optics engine, the glass material, and the
Card / Input / Button styling are preserved pixel-for-pixel. The rest of the
component set (Modal, Navbar, Select, Alert, Checkbox, Radio, Switch, …) is
built in the exact same design language.
- 🧊 Real refraction — the SVG-displacement engine, ported byte-for-byte.
- ⚛️ Idiomatic React — hooks, refs, controlled/uncontrolled, no manual DOM.
- 🌲 Tree-shakeable — ESM + CJS, side-effect-free JS, per-component imports.
- 🔡 Typed — written in TypeScript, ships
.d.tsdeclarations. - ♿ Accessible — roles,
aria-*, focus management, keyboard support.
Install
npm install ios-liquid-glass-stylereact and react-dom (>= 17) are peer dependencies.
Setup
Import the stylesheet once, at your app root:
import "ios-liquid-glass-style/styles.css";Then use the components. Two import styles are supported:
// 1) Namespace — great for discoverability
import { Liq } from "ios-liquid-glass-style";
<Liq.Card title="Liquid glass">
<Liq.Input placeholder="Enter your name" />
<Liq.Button>Submit</Liq.Button>
</Liq.Card>
<Liq.Alert type="success" message="Successfully saved!" />// 2) Named — best for tree-shaking
import { Button, Card, Input, Alert } from "ios-liquid-glass-style";Both resolve to the same components. Named imports let your bundler drop any component you don't use.
Give the glass something to refract
Refraction only shows when there's colorful content behind the glass. Drop the
included Background behind your app (it's the exact mesh from the original
demo), or use your own imagery:
import { Background, Card } from "ios-liquid-glass-style";
<>
<Background />
<Card title="Liquid glass" style={{ position: "relative" }}>…</Card>
</>The Liquid Glass engine
Any element can wear the material via the useLiquidGlass hook (every surface
component uses it internally):
import { useRef } from "react";
import { useLiquidGlass } from "ios-liquid-glass-style";
function Panel() {
const ref = useRef<HTMLDivElement>(null);
const { supported, refresh } = useLiquidGlass(ref);
return <div ref={ref} className="liq-surface">…</div>;
}Engine options
Passed via the glassOptions prop on Glass/Card/Modal/Navbar/Button
(variant="glass"), or as the second argument to useLiquidGlass. Defaults are
identical to the original module:
| Option | Default | Meaning |
|---|---|---|
| scale | -112 | displacement strength; negative = magnifying bulge (−60 subtle … −180 dramatic) |
| chroma | 6 | per-channel scale stagger (prism fringe); 0 disables |
| border | 0.07 | neutral interior inset, fraction of the smaller side |
| mapBlur | 12 | bulge curvature: small = hard rim, large = dome |
| blur | 3 | backdrop blur inside the glass |
| saturate | 1.5 | backdrop saturation boost |
| radius | null | corner-radius override (px); defaults to the element's border-radius |
| fallbackBlur | 16 | frosted blur on Safari/Firefox |
useLiquidGlass(ref, options, enabled?) returns { supported, refresh } —
supported is false on Safari/Firefox (frosted fallback applied); refresh()
regenerates the displacement map (also runs automatically on element resize).
Browser support. Real refraction is Chromium-only. On Safari and Firefox the components automatically fall back to a frosted blur — never let the refraction carry meaning, only delight.
Performance
SVG-displacement backdrops are GPU-heavy, and a page with many live glass surfaces can get janky. The library keeps things smooth without changing how anything looks:
- Viewport-gated refraction (
lazy, on by default) — a surface only runs its backdrop filter while it's near the viewport, so scrolling a long, glass-dense page doesn't composite every off-screen filter each frame. Visible surfaces are pixel-identical. Disable per surface withlazy={false}, or tune how early it kicks in withrootMargin. - Cached displacement maps — identical-size surfaces share one generated map
instead of each running a canvas
toDataURL(), so re-activating on scroll (and first paint of many same-size cards) is nearly free. - rAF-batched dragging —
useDraggablewrites one update per frame.
If a view is still heavy, the cheapest lever is fewer simultaneously visible
refracting surfaces — set refract={false} on secondary panels (they keep the
exact glass tint + shadow, just without the live backdrop bend), or lower
glassOptions={{ blur, chroma }}. None of these change the resting look of the
surfaces you leave refracting.
Theming
Every value is a CSS custom property (see tokens.css). Override any of them on
:root (or a scope) to re-theme the whole library without touching components:
:root {
--liq-accent: #7c5cff; /* checkboxes, radios, switches, focus accents */
--liq-radius: 24px; /* surface corner radius */
--liq-radius-control: 12px; /* inputs / buttons */
}Key tokens: --liq-bg, --liq-fg, --liq-fg-muted, --liq-surface-bg,
--liq-surface-shadow, --liq-control-bg, --liq-control-border,
--liq-control-border-focus, --liq-accent, --liq-success, --liq-warning,
--liq-danger.
Per-component styling & color
Every component forwards className and style to its root element, so
you can style — or recolor — a single instance without any global CSS. The
cleanest way to recolor one instance is to override the CSS variable it reads,
right in style:
// just these instances — no global theme change
<Checkbox style={{ "--liq-accent": "#ff5e7e" } as React.CSSProperties} />
<Switch style={{ "--liq-accent": "#2fd4a7" } as React.CSSProperties} />
<Button style={{ "--liq-btn-bg": "#ff5e7e", "--liq-btn-fg": "#0a0a12" } as React.CSSProperties}>Pink</Button>
<Input label="Tinted" style={{ "--liq-control-bg": "rgba(157,92,255,.18)" } as React.CSSProperties} />(The as React.CSSProperties cast is only needed to satisfy TypeScript for the
custom -- properties.) Which variable each component reads to recolor:
| Component(s) | Override to recolor |
|---|---|
| Card, Glass, Modal, Navbar, Alert | --liq-surface-bg, --liq-surface-shadow |
| Button (primary) | --liq-btn-bg, --liq-btn-fg |
| Input, Textarea, Select | --liq-control-bg, --liq-control-border, --liq-control-border-focus |
| Checkbox, Radio, Switch | --liq-accent |
| Alert (per type) | --liq-alert-accent (or --liq-success / --liq-warning / --liq-danger / --liq-accent) |
Use plain inline styles for one-off layout (style={{ marginTop: 12 }}), a
className for reusable rules, and the CSS-variable overrides above for color.
For labelled fields (Input/Textarea/Select) className/style land on the
field root; target the control itself via its .liq-input / .liq-textarea /
.liq-select__trigger class.
Components
All components forward refs, accept className + style, and spread native
props. See docs/ for a page per component, and run the playground for
a live sandbox:
npm run dev # opens the playground| Component | Import | Description |
|---|---|---|
| Glass | Glass | The material primitive — build custom glass surfaces. |
| Card | Card (.Title .Description .Row) | The flagship draggable glass panel. |
| Button | Button | primary (exact demo) / secondary / ghost / glass. |
| Input | Input | Text field with optional label / hint / error. |
| Textarea | Textarea | Multiline field with auto-resize. |
| Select | Select | Accessible listbox with a glass popover + typeahead. |
| Checkbox | Checkbox | Glass checkbox with indeterminate state. |
| Radio | Radio, RadioGroup | Radio group via context. |
| Switch | Switch | Toggle with role="switch". |
| Alert | Alert | Status banner (info/success/warning/error). |
| Modal | Modal / Dialog (.Header .Body .Footer) | Portalled dialog with focus trap. |
| Navbar | Navbar (.Brand .Section .Item) | Glass navigation bar. |
| Kbd | Kbd, Keyboard | Keyboard key caps + layouts. |
| Background | Background | The demo's mesh + type backdrop to refract against. |
| Hooks | useLiquidGlass, useDraggable, isSupported | The engine + drag, as hooks. |
Development
npm install
npm run dev # playground (Vite)
npm run build # dist/ — ESM + CJS + d.ts + styles.css
npm run typecheck # tsc --noEmitAuthors
- Amandeep — author
- Pankaj — supporter
Credits
Technique informed by Aave's "Building glass for the web"
and rizroze/liquid-glass. Ported from
the original liquid-glass vanilla module.
License
MIT #� �L�i�q�u�i�d�e�-�g�l�a�s�s� � �
