Effects
Glass Scene
Draws the glass inside it as real bevelled glass that bends its backdrop and melts together.
pnpm dlx shadcn@4.14.1 add @glassyui/glass-scenePreview
Liquid Glass
Light bends at every edge.
Demos ready. Update 0.
Installation
First add the exact @glassyui mapping to components.json as shown in Getting Started. Then run the pinned item command:
pnpm dlx shadcn@4.14.1 add @glassyui/glass-sceneWhat this installs9 files, 1 package
Direct registry declarations
These values belong to @glassyui/glass-scene itself in registry.json.
Package dependencies
clsx
Registry dependencies
@glassyui/glass-provider
File targets
components/glassyui/glass-scene.tsxcomponents/glassyui/lib/merge-refs.tscomponents/glassyui/lib/scene-context.tscomponents/glassyui/lib/scene-renderer.tscomponents/glassyui/lib/liquid-motion.ts
Complete installed closure
This follows every namespaced registry dependency recursively and de-duplicates the packages and target files installed with this item.
Package dependencies
clsx
Installed files
components/glassyui/glass-scene.tsxcomponents/glassyui/lib/merge-refs.tscomponents/glassyui/lib/scene-context.tscomponents/glassyui/lib/scene-renderer.tscomponents/glassyui/lib/liquid-motion.tscomponents/glassyui/glass-provider.tsxcomponents/glassyui/liquid-filters.tsxcomponents/glassyui/lib/preferences.tscomponents/glassyui/styles/liquid.css
Usage
Save this as scene-example.tsx. It is the complete example shown above, including its artwork.
'use client';
import { useState } from 'react';
import { GlassButton } from '@/components/glassyui/glass-button';
import { GlassPanel } from '@/components/glassyui/glass-panel';
import { GlassDrop, GlassScene } from '@/components/glassyui/glass-scene';
const backdrop = `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700">
<defs><linearGradient id="sky" x2="1" y2="1"><stop stop-color="#28140e"/><stop offset="1" stop-color="#a13e24"/></linearGradient><linearGradient id="wave" x2="1" y2="0"><stop stop-color="#ffb88d"/><stop offset=".5" stop-color="#ef542b"/><stop offset="1" stop-color="#a53722"/></linearGradient></defs>
<rect width="1200" height="700" fill="url(#sky)"/>
<path d="M-100 340C180 150 480 580 790 300S1190 230 1300 130V800H-100Z" fill="url(#wave)"/>
<path d="M-100 440C220 210 480 660 790 400S1170 300 1300 210" fill="none" stroke="#ffe2cc" stroke-opacity=".65" stroke-width="3"/>
<path d="M-100 610C180 410 490 770 840 550S1210 560 1300 420V800H-100Z" fill="#481a10"/>
</svg>`)}`;
type SceneExampleProps = { onAction?: (message: string) => void };
export function SceneExample({ onAction }: SceneExampleProps) {
const [merged, setMerged] = useState(false);
const [flowing, setFlowing] = useState(false);
const [message, setMessage] = useState('');
function announce(next: string) {
setMessage(next);
onAction?.(next);
}
return (
<>
<style>{`
.glassy-scene-example { container-type: inline-size; width: 100%; height: 340px; overflow: hidden; border-radius: 22px; }
.glassy-scene-example-card { position: absolute; top: 24px; left: 24px; width: min(260px, calc(100% - 48px)); padding: 18px; }
.glassy-scene-example-drop { position: absolute; border-radius: 50%; transition: right 700ms var(--lg-spring), bottom 700ms var(--lg-spring); }
.glassy-scene-example-large { right: 24px; bottom: 78px; width: clamp(84px, 25cqi, 144px); height: clamp(84px, 25cqi, 144px); }
.glassy-scene-example-small { right: calc(24px + clamp(84px, 25cqi, 144px) + 34px); bottom: 92px; width: clamp(58px, 17cqi, 98px); height: clamp(58px, 17cqi, 98px); }
.glassy-scene-example[data-merged="true"] .glassy-scene-example-small { right: calc(24px + clamp(84px, 25cqi, 144px) - 18px); bottom: 106px; }
.glassy-scene-example-actions { --lg-scene-merge: 0px; position: absolute; bottom: 22px; left: 24px; display: flex; gap: 8px; }
[data-lg-reduce-motion="1"] .glassy-scene-example-drop { transition: none; }
@media (prefers-reduced-motion: reduce) { .glassy-scene-example-drop { transition: none; } }
`}</style>
<GlassScene
backdrop={backdrop}
className="glassy-scene-example"
data-merged={merged}
flow={flowing}
optics={{ merge: 24 }}
>
<GlassDrop className="glassy-scene-example-drop glassy-scene-example-large" />
<GlassDrop className="glassy-scene-example-drop glassy-scene-example-small" />
<GlassPanel className="glassy-scene-example-card" variant="card">
<p style={{ margin: 0, fontSize: 12, fontWeight: 600 }}>Liquid Glass</p>
<p style={{ margin: '6px 0 0', fontSize: 22, fontWeight: 700, lineHeight: 1.15 }}>
Light bends at every edge.
</p>
</GlassPanel>
<div className="glassy-scene-example-actions">
<GlassButton
aria-pressed={merged}
onClick={() => {
setMerged(!merged);
announce(`Drops ${merged ? 'apart' : 'merged'}.`);
}}
size="sm"
variant="prominent"
>
Merge
</GlassButton>
<GlassButton
aria-pressed={flowing}
onClick={() => {
setFlowing(!flowing);
announce(`Flow ${flowing ? 'off' : 'on'}.`);
}}
size="sm"
>
Flow
</GlassButton>
</div>
</GlassScene>
{!onAction ? (
<span
role="status"
style={{
position: 'absolute',
width: 1,
height: 1,
overflow: 'hidden',
clipPath: 'inset(50%)',
}}
>
{message}
</span>
) : null}
</>
);
}
After configuring the registry, install every component this example uses:
pnpm dlx shadcn@4.14.1 add @glassyui/glass-button @glassyui/glass-panel @glassyui/glass-sceneExamples
import { SceneExample } from "./scene-example";
export default function Page() {
return <SceneExample />;
}API
GlassSceneStage that paints its backdrop and draws the glass inside it on one WebGL2 canvas.
backdropThe image the glass refracts: a URL, or a CSS url() or var() value. Serve it from your origin or with CORS.
flowLets drops ripple like liquid. Reduced motion holds them still. Defaults to false.
opticsior (1.5), thickness (40), bevel (28), dispersion (0.15), frost (1), merge (12) and specular (1). Lengths are CSS pixels.
GlassDropA plain drop of glass. Size, round and place it with CSS.
mergeGlassDrop only: this drop’s merge distance in pixels. Defaults to the scene’s.
Notes
What joins the scene
GlassSurface, GlassPanel, GlassButton and GlassDrop inside a scene hand their glass to it. Ghost buttons, glass nested in other glass and surfaces with a generated refraction keep their own. Nested glass drops its shadow and tints away from the pane’s text, so its labels stay legible.
Legible text
Text on scene glass keeps 4.5:1 over any backdrop: where the backdrop is too bright or too dark, the face dims or lifts just enough and the rim keeps its light. Each piece protects the colour in --lg-scene-ink, the muted text on surfaces and the label on buttons. Set it to transparent on glass without text.
Optics
Each piece is a thick slab floating just above the backdrop. Light bends through its top, its body and the air beneath, so steeper rims and a higher ior bend more, and dispersion splits the bent light into a spectrum. Past the critical angle a rim mirrors the scene instead, and tinted glass absorbs more where it is thicker and casts a tinted shadow.
Browsers
The scene needs WebGL2, which every current browser has. Without it the scene logs one warning and every piece keeps its CSS glass.
Accessibility
Reduced transparency, increased contrast and forced colours switch the canvas off and return the CSS glass. Reduced motion holds the flow still. The canvas never takes clicks or focus.
Accessibility
Keep the Radix-backed semantics intact and preserve visible labels, focus states, and keyboard operation when copying the source.
Performance
Use provider quality preferences for dense app screens. Reserve strong refraction for hero or showcase surfaces.