Layered translucency over depth: frosted surfaces at three elevations, a luminous ground for them to refract, 1px light edges, and soft large-radius shadows. Apple's Liquid Glass direction sits here. So does the whole iOS and macOS translucency lineage that arrived with iOS 7 and has been rebuilt about once a decade since.
The hard part of this register is not the blur. It is keeping text legible on a surface whose background is different everywhere. That section is below, and it is most of what this repo is.
The screenshot above is demo/index.html, a fictional ambient-data product called Pane. Open it live, or clone the repo and open the file. There is no build step, no framework, no node_modules, and no server to start.
The demo declares no color of its own. It links tokens/tokens.css, components/components.css and components/depth.css and reads every value from them, so it stays honest about what the theme actually produces.
There is a second page worth opening: components/index.html renders all five components with their variants and the markup to copy, plus the three elevation tiers and the measured contrast table.
A visual style is a set of coordinates, not a mood. This theme sits at one point in the creative direction framework, which sets brand direction on four axes. Here is where this register lands and what each choice pays for.
| Axis | Position | What the position buys |
|---|---|---|
| Tone register | Conversational | A voice that can say "the screen you stop checking" without either winking or presenting. Glass is a calm material and a loud voice fights it. |
| Aesthetic philosophy | Polished Standard | The honest coordinate. This register is the modern product convention, and the position's own failure mode is looking like every other site in the category. What differentiates has to be craft inside the convention, which is why the contrast work below is the artifact rather than a footnote to it. |
| Audience relationship | Peer | A 12px radius on the control the reader's hand meets. Square would put the brand above them; a pill would put it below. |
| Sensory ambition | Considered | One glow per screen, three orbs, one gesture. The reader registers that the depth was built rather than switched on. |
Those four position names are the exact strings the framework uses. If you want the long version of any of them, the links go to the position page.
Clone once, then pick the path that matches your stack.
git clone --depth 1 https://github.com/rampstackco/glassmorphism-themePlain CSS. Copy the two directories and link the three files in order. This is the whole install.
cp -r glassmorphism-theme/tokens glassmorphism-theme/components your-project/styles/<link rel="stylesheet" href="/styles/tokens/tokens.css" />
<link rel="stylesheet" href="/styles/components/components.css" />
<link rel="stylesheet" href="/styles/components/depth.css" />Then give the page a ground to refract, because without one every panel is a gray box:
<div class="gl-ground" aria-hidden="true">
<!-- three inlined orbs from assets/, then: -->
<div class="gl-veil"></div>
</div>Copy that block out of demo/index.html. It has to be inlined rather than loaded through <img>, for reasons under the depth layer.
Tailwind v4. One import. theme.css pulls in tokens.css and maps it onto Tailwind's theme namespaces, so you get bg-gl-surface-2, shadow-gl-2, rounded-gl-lg, text-gl-h1.
@import "tailwindcss";
@import "./styles/tokens/theme.css";Tailwind v3. Load the tokens in your stylesheet, then register the preset.
@import "./styles/tokens/tokens.css";
@tailwind base;
@tailwind components;
@tailwind utilities;// tailwind.config.js
module.exports = {
presets: [require("./styles/tokens/preset.js")],
content: ["./src/**/*.{html,js,jsx,ts,tsx}"],
};Already on shadcn/ui. The tokens are namespaced --gl-* so they will not clobber yours. Bridge the two in your global stylesheet and shadcn's components inherit the register:
:root {
--background: var(--gl-ground);
--foreground: var(--gl-ink);
--card: var(--gl-surface-2);
--popover: var(--gl-scrim);
--primary: var(--gl-primary);
--primary-foreground: var(--gl-primary-ink);
--border: var(--gl-edge-boundary);
--ring: var(--gl-ring);
--radius: var(--gl-radius);
}Note --popover. A popover floats over content rather than over the ground, so it takes the scrim rather than a surface. That is rule 3, and it is the one bridge line people get wrong.
A contrast ratio quoted against an opaque background is a fact. Quoted against a translucent one it is a claim about every backdrop that surface might ever sit over, and most CSS glassmorphism generators quote it anyway, having measured one screenshot. The failures that produces are specific: they show up where a gradient happens to be bright, or where light content happens to scroll past, so they survive review and reach production.
This theme bounds the problem in four places.
1. The ground has a computable ceiling. It is a base color plus three orbs, each painted at a known peak alpha. Source-over compositing makes every rendered channel a weighted average of those inputs, so the lightest pixel the ground can produce can be calculated rather than observed. That number is --gl-ground-peak, and every ink ratio in the theme is measured against it rather than against a screenshot. It has also been checked the other way: no pixel of the demo's rendered ground exceeds it on any channel, at 1280 or at 390.
2. Small text gets an alpha floor. A paragraph can absorb a varying backdrop; a 12px uppercase badge label cannot. At a decorative 0.20 alpha the semantic fills put their label at 4.47:1 in the info case and 4.08:1 in the danger case, both of which fail, and both of which only fail where an orb happens to be. --gl-alpha-floor is 0.62, high enough that the fill rather than the backdrop decides what the label sits on. The same two cases then measure 6.58:1 and 6.37:1.
3. Surfaces over content get a scrim. This is the one that is easy to miss, because the ceiling in point 1 only covers surfaces sitting on the ground. A sticky bar's backdrop is the document, and the document contains ink. Measured on a tier-2 fill with body text scrolling underneath, a nav link lands at 1.24:1. --gl-scrim is the ground color at an alpha that puts a floor under any backdrop at all: over pure white content the same link holds 5.27:1. The nav takes it, popovers take it, and tier 3 carries it without being asked.
4. The edges are structural, not decorative. On this ground a tier-1 panel measures 1.17:1 against the darkest ground it can sit over. A glass fill cannot carry its own boundary at any alpha this register permits; reaching 3:1 on fill alone needs roughly 0.34, which is a painted surface rather than a pane. So the light edge is not a highlight that makes it look nice. It is the only thing telling the reader where the panel ends, which is why --gl-edge-strong exists as a separate token for anything a pointer can act on, and why it is the one edge that has to clear 3:1 against the lightest panel underneath it.
Every derived number above is recomputed by verify/contrast.js, which reads the real token file rather than a copy and exits non-zero when a value drifts away from the ratio written next to it.
node verify/contrast.jsNo dependencies, Node 18 or newer. It also checks the claims that are not ratios: that blur and alpha rise together across the tiers, that tier 3 still carries the scrim, that the alpha floor is still load-bearing rather than habit, and that the one percentage color-mix() forces components.css to spell out still matches its token.
prefers-reduced-transparency: reduce swaps every surface for a solid, zeroes the blur, and removes the orbs and the veil. The tiers survive as three opaque values in the same order, so hierarchy built out of elevation still reads, and ink improves on all of them, to 13.35:1 at tier 2. prefers-contrast: more retires the material entirely. Shipping glass without that first branch is this register's most common accessibility failure, and it is a real setting on macOS, iOS and Windows.
tokens/tokens.css is the single source of truth. Every literal value in the theme appears there exactly once; theme.css and preset.js hold no values of their own and point back at it with var(). Change a hex there and the demo, the components, the depth layer, and both Tailwind adapters follow.
The file is annotated. Each group of tokens carries a comment naming the axis the choice serves and why, so the elevation tiers explain themselves:
/* GLASS: THE ELEVATION TIERS
Sensory ambition axis. Three tiers, and the pairing of blur with alpha is
the whole trick. A surface nearer the viewer occludes more of what is
behind it and defocuses it further, so alpha and blur rise together. Move
one without the other and the tier stops reading as a distance and starts
reading as a style. */CUSTOMIZE.md is the half-finished layer, and it is half-finished deliberately. It documents retheming as axis moves rather than as a color picker: pick an axis, move along it, change the two or three tokens that carry the move. One move is worked through with before and after values. Two more are sketched so the format is obvious enough to finish yourself.
Tokens can carry a blur radius and an alpha. They cannot carry the thing that makes those worth having, which is something behind the glass to refract. That half lives in assets/ and components/depth.css.
assets/ holds four original SVGs: three orbs and a brand mark. They are built to be inlined into your markup rather than loaded through <img>, because an SVG inside an <img> is an isolated document that cannot read your custom properties. Inlined, the paint rules in depth.css reach them and they take your token colors and your orb alpha. Opened on their own they stay legible as plain shapes.
depth.css adds the ground, the veil, the three tiers as classes, and the stacking rules. Two of its parts are worth knowing about before you build anything with this register.
The veil is a fine grid at low opacity between the orbs and the glass, and the numbers behind it are worth stating because the obvious version of the argument is wrong. Blur is a local average, so blurring a smooth gradient returns something close to the gradient: measured, turning backdrop-filter on over the bare ground changes a panel's pixels by a mean of 1.86/255. Adding the veil takes that to 2.12/255. Neither is visible, so the veil is not what makes the blur "work" across a panel.
What it does is local to the edge. Scanned across the open ground the grid reads at a standard deviation of 0.88; inside a panel it measures 0. Crisp outside, gone underneath, and that discontinuity at the boundary is what the eye reads as a frosted surface.
The stacking rules are three enforced in CSS and one that cannot be:
-
One
backdrop-filterper stack. A filtered surface inside a filtered surface blurs its parent's finished output rather than the page, so the alphas multiply instead of adding and you get a milky panel at twice the compositing cost. Nested surfaces keep their fill and drop their filter. -
A tier cannot sit on itself. Two surfaces at the same alpha, one inside the other, leave a boundary the reader can only find by its border.
-
A surface over content takes a scrim, not a tier. The reason is point 3 above.
-
Nothing between a surface and the ground may carry
opacitybelow 1 or afilter. Either one establishes a backdrop root, the backdrop stops there, and every panel underneath quietly loses its blur while keeping its fill. There is no selector for "my ancestor has an opacity", so this one lives in a comment. It is the most common reason a glass theme renders correctly in a demo and as flat translucent rectangles in a real application.This rule is usually quoted with
transformin the list as well. That part did not reproduce, anddepth.cssships the measurement: against a striped backdrop with a no-filter negative control and a no-wrapper positive control, an ancestortransform(2D, 3D, orwill-change) left the blur intact, whileopacity: 0.99andfilter: blur(0px)destroyed it. A transform does still create a containing block, which will pull a fixed-position ground out from under the page and produce flat panels by a different route. Engines have differed here historically; those figures are one engine, which is more than the folklore has.
Unlike the neobrutalism sibling's play layer, this one is not a free deletion. Mechanically it is separable, and nothing outside it refers to it. Visually, deleting it leaves gray boxes with light borders, because glass is the one material whose appearance is entirely a function of what is behind it. CUSTOMIZE.md treats that as a fact about the register rather than as an axis move.
Consuming this from a Claude skill. The design-standards skill asks for a project's design tokens as a required input and offers to define a working set when none exist. Point it at tokens/tokens.css instead. The file already covers every category the skill asks for, in the order it asks: color with measured contrast ratios, spacing scale, type scale, radius. Every text pairing in it clears WCAG AA at the worst case, and the ratios are in the comments, so the skill's contrast pass has nothing left to compute.
Glassmorphism sits in a family of soft, dimensional registers that get confused with each other constantly. They are not variants of one idea, and only two of them are things you should be building today.
The ancestor, and the one with the traffic: around 5,800 searches a month, most of it curiosity rather than intent. Skeuomorphism gives a digital control the appearance of the physical object it replaced. A notes app with stitched leather. A recorder with a real VU meter. Its argument was pedagogical: a reader who had never used a touchscreen could see what a thing did.
That argument expired. Everyone knows what a button is now, so the leather is cost without benefit, and the cost is real, because a skeuomorphic control is bespoke art rather than a system.
Glassmorphism is not a revival of it. Skeuomorphism imitates a specific object; glass imitates a material property, refraction, and applies it uniformly to every surface in a system. One produces a drawer that looks like a drawer, the other produces a rule about what any surface does to what is behind it. If you arrived here from a skeuomorphism search wanting the history, the honest answer is that it is history. If you arrived wanting depth that ships, this repo is the closest live thing to it.
The 2020 attempt at the same feeling: soft extruded shapes on a matched background, lit from one corner, with paired light and dark shadows that make a control look pressed out of the surface rather than placed on it.
It has a known accessibility problem, and it is structural rather than a matter of execution. A neumorphic control is the same color as its background by definition. Its only boundary is the shadow pair, which routinely lands under 3:1, and there is no way to fix it without leaving the register. The comparison people usually want is exactly this: glass has the same boundary problem, which is point 4 above, and glass can solve it by drawing an edge because a glass surface is allowed to differ from its ground. Neumorphism cannot, because being the same color as the ground is the whole idea.
If you want to build in that register anyway, existing generators serve it well and this repo has nothing to add. It is not shipped here.
Not a separate register. Aurora is a recipe: large, soft, saturated color fields bleeding into each other behind content. It is this repo's ground with the veil turned off and the orbs turned up.
:root {
--gl-orb-alpha: 0.34; /* recompute --gl-ground-peak, then rerun verify */
--gl-orb-size: 88vmax;
--gl-veil-opacity: 0;
}The comment matters. Raising the orb alpha lifts the ground's ceiling, which moves every ink ratio in the theme, so --gl-ground-peak has to be recomputed and node verify/contrast.js has to pass before you ship it. Aurora backgrounds are where the contrast failures in this family come from, and the reason is always the same: the gradient was chosen for how it looked in the hero and text was put on it afterward.
Apple's 2025 direction and the reason this register is having a second life. It goes beyond a frosted fill to real-time refraction, specular highlights that respond to motion, and surfaces that bend what is behind them rather than only blurring it. Most of that needs a renderer the web does not hand you, so what ships as "Liquid Glass" on the web is usually this register plus a highlight sweep. That is worth knowing before you promise a client the Apple thing.
The parts that do translate are here: the light edge, the saturation lift behind the blur, and the elevation tiers.
Inflated 3D shapes, high radii, soft double shadows, sweet palette. It shares this register's softness and none of its transparency, and it is a tone-register move toward Playful rather than a depth system.
Real products mix registers rather than adopting one whole. PostHog layers glassy translucent accents over a neobrutalist structural foundation: hard borders and flat fills doing the structure, glass doing the surfaces that sit above it. Composing the two is intended use, and the two halves live in two repos: the structural one is neobrutalism-theme, and its README points back here for the surfaces.
When you do mix them, the stacking rules still apply, and rule 4 is the one that bites. The thing to watch for is a wrapper carrying opacity or a filter between a glass panel and the ground, which is easy to introduce with a fade-in or a hover treatment and which silently leaves the panel frosting nothing.
saas-landing-theme sits at the same four coordinates this theme does. That is not a duplicate: the two work on different layers. That one settles the spacing, the type scale and the contrast of a page; this one replaces what the surfaces are made of and leaves the arrangement where it was.
The order matters more here than in most pairings. Glass is a material rather than a layout, so it inherits whatever composition it is applied to, including a bad one. A page that is not working at the base position will not start working because its panels became translucent, and the frosting will make the diagnosis harder by hiding the edges you would have used to see the problem. Get the page right there first, then bring it here.
MIT. See LICENSE. Use it commercially, fork it, rename the tokens, ship it. No attribution required.
Issues and pull requests are welcome here. For questions, ideas, and anything conversational, use the discussions on the claude-skills repo, which is where all discussion for these repos lives.
