/* The API reference for @colineapp/liquid-glass, as data. The page's reference
section renders it, and scripts/agent-docs.ts writes it out as plain text for
agents (llms.txt, agents.md, /docs/*.md, /options.json), so both say the same
thing. Keep it in step with the package's types. */
export const PACKAGE = "@colineapp/liquid-glass";
export const VERSION = "0.1.0";
export const INSTALL = `npm install ${PACKAGE}`;
// The site's address, which `coline-app deploy --name liquid-glass` gives it.
export const SITE_URL = "https://liquid-glass.coline.space";
export const NPM_URL = `https://www.npmjs.com/package/${PACKAGE}`;
export const SUMMARY =
"Liquid Glass for the web: the rim refraction, light and motion of iOS 27's glass on any rounded element, with React bindings and tabs whose thumb lifts into a lens. MIT.";
export type ApiEntry = {
/** Option, method or export name. */
name: string;
type: string;
default?: string;
description: string;
/** A short example an agent can copy as is. */
example?: string;
};
export type ApiGroup = {
id: string;
title: string;
intro: string;
entries: ApiEntry[];
};
export const GLASS_OPTIONS: ApiGroup = {
id: "options",
title: "Glass options",
intro:
"Options for ``, `useLiquidGlass(ref, options)`, `createLiquidGlass(element, options)` and `glass.update(options)`. Every option is optional.",
entries: [
{
name: "appearance",
type: '"system" | "class" | "dark" | "light"',
default: '"system"',
description:
'`system` follows prefers-color-scheme. `class` is dark while `` has the `dark` class (Tailwind\'s class strategy, next-themes). `dark` and `light` fix it.',
example: '',
},
{
name: "variant",
type: '"regular" | "clear" | "lens"',
default: '"regular"',
description:
"`regular` diffuses and evens out the page so text on the glass stays legible over anything. `clear` only bends and lights the page, for glass over media. `lens` is the clear lens a control lifts into; pair it with `geometry: LENS_GEOMETRY`.",
example: '',
},
{
name: "floating",
type: 'boolean | "dialog"',
default: "false",
description:
'For menus, popovers, dialogs and sheets. Floating glass diffuses the page more, as iOS 27\'s does, so text beneath washes into soft colour, and the surface casts a drop shadow (`--liquid-glass-shadow`). `"dialog"` casts a dialog\'s deeper shadow (`--liquid-glass-dialog-shadow`).',
example: '',
},
{
name: "interactive",
type: "boolean",
default: "false",
description:
"For glass that holds controls, like SwiftUI's `.glassEffect(.regular.interactive())`: the pointer warms the glass beneath it, and a press blooms out from there.",
example: "",
},
{
name: "swell",
type: "boolean",
default: "false",
description:
"For glass you press, like a search field: pressed, the surface swells about its centre on the tabs' spring and springs back with a slight overshoot on release.",
example: "",
},
{
name: "tint",
type: "string",
default: "near-black in dark, white in light",
description:
"Colour washed over the body by the tone's `tint` share (50% on dark regular glass, 40% on light). Any CSS colour, including `var()`.",
example: '',
},
{
name: "hue",
type: "string | null",
default: "null",
description:
"A colour whose hue the glass takes on, like tinted iOS glass: each pixel beneath shifts toward the hue at its own brightness, so colours behind keep their chroma. Any concrete CSS colour; neutral colours leave the glass untinted.",
example: '',
},
{
name: "hueStrength",
type: "number (0–1)",
default: "1 on light glass, 0.5 on dark",
description: "How much of the hue the glass takes.",
example: '',
},
{
name: "geometry",
type: "Partial",
default: "DEFAULT_GEOMETRY",
description:
"The rim's shape, measured from iOS 27: its width and depth, how steeply it bends, the darkened edge and the glints. See the geometry fields below. `LENS_GEOMETRY` is the rim for a control-high lens.",
example: "",
},
{
name: "tone",
type: "{ dark?: Partial; light?: Partial }",
description:
"Per-appearance overrides of the variant's filters: body `blur`, `floatingBlur`, `saturate`, `contrast`, `brightness`, `tint` share, and the rim's `highlightContrast`, `highlightBrightness`, `shadeBrightness`, `glowSaturate`, `glowBrightness`, `edgeBrightness`.",
example: "",
},
{
name: "fit",
type: '"box" | "fixed" | "scroll"',
default: '"box"',
description:
"`box` fills the surface. `fixed` fills the nearest containing block for fixed elements, such as a transformed popper wrapper, so the glass holds still while the surface scrolls its own content. `scroll` spans the surface's whole scroll height, for a dialog or sheet that scrolls itself.",
example: '',
},
{
name: "refraction",
type: "boolean",
default: "true",
description:
"Bend the page at the rim. Rim refraction needs SVG filters in backdrop-filter, which only Chromium renders; Safari and Firefox keep the material without the bend either way.",
example: "",
},
{
name: "samples",
type: "1 | 4",
default: "1",
description:
"Refraction samples per pixel. 4 antialiases the bend, so edges it stretches don't stair-step as the glass moves. It costs four displacement passes, so keep it for small glass with nothing blurring its backdrop, like a lens.",
example: '',
},
],
};
export const GEOMETRY_FIELDS: ApiGroup = {
id: "geometry",
title: "Geometry fields",
intro: "Fields of `GlassGeometry`, in CSS pixels unless noted. Pass any subset as `geometry`.",
entries: [
{ name: "bezel", type: "number", default: "17", description: "Width of the refracting rim along the top and bottom. The centre is flat." },
{ name: "thickness", type: "number", default: "24", description: "Glass depth under the top and bottom rims. Deeper glass bends further." },
{ name: "sideBezel", type: "number", default: "27", description: "Width of the refracting rim at the sides and rounded ends." },
{ name: "sideThickness", type: "number", default: "44", description: "Glass depth under the sides and rounded ends." },
{ name: "refractiveIndex", type: "number", default: "1.5", description: "The glass's refractive index, for Snell's law at the rim." },
{
name: "maxFall",
type: "number (0–1)",
default: "0.8",
description:
"Rim bend: the steepest the rim's shift may fall per pixel inward. Where it falls, the rim magnifies what is beneath by 1 / (1 − maxFall), so lower values bend more gently and keep text crossing the rim from smearing. The tab lens uses 0.3. Past 1 the lens folds content into mirrored echoes.",
},
{ name: "edge", type: "number", default: "0.75", description: "Width of iOS 27's darkened edge, a contour where the glass meets the air, with the hairline just inside it. 0 leaves it out." },
{
name: "glint",
type: "number (0–1)",
default: "0.4",
description: "Share of the hairline's light that gathers into glints where the light catches the curve, at the top-left and, fainter, the bottom-right. 0 keeps an even band.",
},
{ name: "hairline", type: "number", default: "1", description: "Rim hairline width." },
{ name: "hairlineSides", type: "number (0–1)", default: "0.15", description: "Share of the hairline's strength that reaches the sides." },
{ name: "shadeWidth", type: "number", default: "1", description: "Width of the darkened sides." },
{ name: "glowReach", type: "number", default: "8", description: "How far the inner glow reaches in from the top and bottom rims." },
{ name: "glowBottom", type: "number (0–1)", default: "0.5", description: "Bottom glow strength relative to the top." },
],
};
export const HANDLE_METHODS: ApiGroup = {
id: "handle",
title: "The glass handle",
intro:
"`createLiquidGlass()` and `attachLiquidGlass()` return it; `useLiquidGlass()` returns a ref to it, and `` fills one.",
entries: [
{ name: "update(options)", type: "(options: LiquidGlassOptions) => void", description: "Replaces the options. Cheap: the rim only rebuilds when the geometry changes.", example: 'glass.update({ variant: "clear" });' },
{
name: "magnify(factor)",
type: "(factor: number) => void",
description: "Magnifies what is beneath the glass about its centre, as a raised lens does (1 for none), and sharpens it so it doesn't come out soft. Cheap enough to call every frame. Needs refraction.",
example: "glass.magnify(1.1);",
},
{
name: "materialize(timing?)",
type: "(timing?: { duration?, easing?, delay?, scale? }) => Promise",
description:
"Grows the glass from clear to its tone, for surfaces that appear. Fade the surface's contents, never the surface: opacity below 1 hides the glass. For a surface that zooms open, scale its contents and pass the same `scale`; the glass grows by its inset instead.",
example: "await glass.materialize({ duration: 240, scale: 0.96 });",
},
{ name: "dematerialize(timing?)", type: "(timing?) => Promise", description: "Fades the glass back to clear and holds it there, for surfaces that leave. Wells on it ease back with it.", example: "await glass.dematerialize({ duration: 160 });" },
{ name: "destroy()", type: "() => void", description: "Removes the glass and its filters." },
{ name: "element", type: "HTMLElement", description: "The glass element, the surface's first child." },
{ name: "appearance", type: '"dark" | "light"', description: "The resolved appearance." },
],
};
export const TABS_OPTIONS: ApiGroup = {
id: "tabs",
title: "Tabs",
intro:
"`useLiquidGlassTabs(trackRef, options)` in React, `attachLiquidGlassTabs(track, options)` without it. The track is the tabs' positioned, rounded container. Your tabs and selection stay yours; the thumb follows the selected tab, lifts into a lens that magnifies the labels, stretches as it travels and squashes as it lands. Dragging the selected tab scrubs across the others. Returns `{ update(), destroy() }`.",
entries: [
{ name: "appearance", type: '"system" | "class" | "dark" | "light"', default: '"system"', description: "As for glass." },
{ name: "orientation", type: '"horizontal" | "vertical"', default: '"horizontal"', description: "Vertical tabs stack and don't scrub." },
{ name: "tabs", type: "string", default: '\'[role="tab"]\'', description: "Selector for the tabs inside the track.", example: "useLiquidGlassTabs(track, { tabs: '[role=\"radio\"]' });" },
{
name: "isSelected",
type: "(tab: HTMLElement) => boolean",
default: 'aria-selected, aria-checked or data-state="active"',
description: "Whether a tab is selected. The thumb follows changes to the default attributes by itself; call `update()` after changes to anything else.",
},
{ name: "onSelect", type: "(tab: HTMLElement) => void", default: "press and click the tab", description: "Selects the tab the thumb was dragged onto." },
{
name: "glass",
type: "boolean | LiquidGlassOptions",
default: "glass unless on another glass surface",
description: "The track's own glass, or its options. On another glass surface the track gets none of its own: it becomes a recessed tint and the thumb a well in that glass.",
},
{ name: "refraction", type: "boolean", default: "true", description: "Bend the page at the rims." },
],
};
export const REACT_API: ApiGroup = {
id: "react",
title: "React",
intro: "From `@colineapp/liquid-glass/react`. React 18 or later; the core package has no dependencies.",
entries: [
{
name: "",
type: "LiquidGlassOptions & { className?, style?, children?, glassRef? }",
description: "Glass behind a surface's content. Render it first inside a positioned, rounded element. `glassRef` receives the handle, for materialize() and magnify().",
example: '',
},
{ name: "useLiquidGlass(ref, options)", type: "RefObject", description: "Turns an element you already render into a glass surface; the glass is inserted as its first child." },
{ name: "useLiquidGlassTabs(ref, options)", type: "RefObject", description: "Liquid Glass tabs on a track you render. See Tabs." },
],
};
export const CSS_API: ApiGroup = {
id: "css",
title: "CSS",
intro: "Import `@colineapp/liquid-glass/styles.css` once. The filters themselves are set inline by the script, as literal values Safari can read.",
entries: [
{ name: ".liquid-glass-well", type: "class", description: "For a selected or hovered control on a glass surface: brightens the glass beneath it (dimmer in light), instead of glass on glass.", example: '' },
{ name: "--liquid-glass-shadow", type: "custom property", description: "The shadow floating glass casts. Set it on the surface or an ancestor." },
{ name: "--liquid-glass-dialog-shadow", type: "custom property", description: 'The deeper shadow of `floating: "dialog"` glass.' },
],
};
export const RULES = [
"Put the glass first inside a positioned (relative, absolute or fixed), rounded element. It takes the element's corner radius and follows its size.",
"Paint nothing over it: no background, border or box-shadow on the surface. The glass draws its own edge.",
"No opacity below 1, filter, mask or backdrop-filter on the surface or its ancestors while the glass shows. Each makes a backdrop root, and the glass would see only that element instead of the page.",
"To show or hide a surface, fade and zoom its contents, never the surface itself, and call materialize() and dematerialize() for the glass.",
"Controls on glass get no glass of their own: give the selected or hovered one the liquid-glass-well class.",
"Rim refraction renders in Chromium. Safari and Firefox get the same material, light and motion without the bend.",
];
export const GROUPS = [GLASS_OPTIONS, GEOMETRY_FIELDS, HANDLE_METHODS, TABS_OPTIONS, REACT_API, CSS_API];
// ── Copy-paste examples ─────────────────────────────────
export type Example = { id: string; title: string; description: string; language: "tsx" | "ts" | "html"; code: string };
export const EXAMPLES: Example[] = [
{
id: "react",
title: "A glass toolbar in React",
description: "Render first inside a positioned, rounded element.",
language: "tsx",
code: `import { LiquidGlass } from "${PACKAGE}/react";
import "${PACKAGE}/styles.css";
export function Toolbar() {
return (
);
}`,
},
{
id: "vanilla",
title: "Without React",
description: "createLiquidGlass inserts the glass as the element's first child.",
language: "ts",
code: `import { createLiquidGlass } from "${PACKAGE}";
import "${PACKAGE}/styles.css";
const glass = createLiquidGlass(document.querySelector(".toolbar")!, {
interactive: true,
});
glass.update({ variant: "clear" });
glass.destroy();`,
},
{
id: "tabs",
title: "Pill tabs",
description: "Your tabs, your selection; the thumb follows aria-selected.",
language: "tsx",
code: `import { useRef } from "react";
import { useLiquidGlassTabs } from "${PACKAGE}/react";
export function Period({ value, onChange }: { value: string; onChange: (value: string) => void }) {
const track = useRef(null);
useLiquidGlassTabs(track, { appearance: "class" });
return (
{["Day", "Week", "Month"].map((label) => (
))}
);
}`,
},
{
id: "menu",
title: "A menu that materializes",
description: "Fade the contents, never the surface; the glass grows from clear.",
language: "tsx",
code: `import { useEffect, useRef, useState } from "react";
import { LiquidGlass, type LiquidGlassHandle } from "${PACKAGE}/react";
export function Menu({ open, items }: { open: boolean; items: string[] }) {
const glass = useRef(null);
const [active, setActive] = useState(-1);
useEffect(() => {
if (open) void glass.current?.materialize({ duration: 240, scale: 0.96 });
}, [open]);
if (!open) return null;
return (
{items.map((item, index) => (
))}
);
}`,
},
{
id: "dialog",
title: "A glass dialog",
description: 'floating="dialog" casts the deeper shadow; the glass materializes as the contents zoom in.',
language: "tsx",
code: `import { useLayoutEffect, useRef } from "react";
import { LiquidGlass, type LiquidGlassHandle } from "${PACKAGE}/react";
export function ShareDialog({ onClose }: { onClose: () => void }) {
const glass = useRef(null);
useLayoutEffect(() => {
void glass.current?.materialize({ duration: 280, scale: 0.95 });
}, []);
const close = () => void glass.current?.dematerialize({ duration: 180, scale: 0.97 }).then(onClose);
return (
Share this album
);
}`,
},
{
id: "search",
title: "A search field that swells",
description: "interactive lights it under the pointer; swell springs it on press.",
language: "tsx",
code: ``,
},
{
id: "card",
title: "A glass card",
description: "Regular glass on a rounded box: the rim bends what crosses it, and the body keeps the card's text legible.",
language: "tsx",
code: `import { LiquidGlass } from "${PACKAGE}/react";
import "${PACKAGE}/styles.css";
export function Card() {
return (
Now playing
Glass takes the box's radius and follows its size.