Theming
68 primitives in 10 groups. Pick presets, turn any knob, theme a single card — all with CSS variables.
Every primitive as a control, a hue-harmony generator, a live preview, and export as a one-line shadcn CLI install, CSS, a shadcn item or a link.
How it works
The glass-style foundation adds primitives to :root and .dark. Everything you see — the frost, the rim, the shadow, the corners, the control heights — is computed from them on the element that uses them. So a primitive can be overridden anywhere, and only that subtree changes:
<Card className="[--glass-blur:6px] [--glass-opacity:70%]">Less blur, more frost</Card>
<Button className="[--glass-radius-button:0.5rem]">Squarer</Button>
<section className="[--glass-density:0.85]">…everything inside is compact</section>Presets compose
Each preset is a shadcn theme item that sets only its own group, so you mix them: a palette, a material, a shape, a motion, a density. Adding one replaces the previous choice in its group.
pnpm dlx shadcn@latest add https://glasscn.app/r/theme-lagoon.json https://glasscn.app/r/material-liquid.json https://glasscn.app/r/shape-soft.jsonPalettes
theme-dusktheme-oceantheme-rosetheme-sagetheme-ambertheme-graphitetheme-lavendertheme-minttheme-cherrytheme-lagoontheme-sandtheme-midnightMaterials
Shapes
Motion
Density
At runtime
The engine (glass-theme) builds themes in code — any hue and harmony, presets, overrides per scheme — and ThemeScope applies one to a subtree. Values are sanitised before they become CSS, so a theme from a share link can’t inject anything.
pnpm dlx shadcn@latest add https://glasscn.app/r/theme-scope.jsonimport { ThemeScope } from "@/components/glass/theme-scope"
import { createGlassTheme } from "@/lib/glass-theme"
const brand = createGlassTheme({
palette: { hue: 200, harmony: "triadic", chroma: 0.18 },
material: "liquid",
shape: "soft",
density: "compact",
tokens: { "glass-blur": "20px" },
dark: { "glass-glow": "24%" },
})
<ThemeScope theme={brand}>…</ThemeScope>Every primitive
Colour
--primaryButtons, selection, rings, the first chart colour.oklch(0.657 0.19 34.8)--primary-foregroundText and icons on the accent.oklch(0.99 0 0)--ringKeyboard focus outlines.oklch(0.657 0.19 34.8)--chart-1First data series (rings, sparklines).oklch(0.657 0.19 34.8)--chart-2Second data series.oklch(0.685 0.189 296.4)--chart-3Third data series.oklch(0.803 0.159 69.8)--chart-4Fourth data series.oklch(0.7 0.14 160)--chart-5Fifth data series.oklch(0.68 0.15 230)--aurora-1Top-left blob.oklch(0.829 0.098 37.8)--aurora-2Right blob.oklch(0.809 0.109 296.4)--aurora-3Bottom blob.oklch(0.907 0.07 70.2)--aurora-baseThe page colour under the aurora.oklch(0.955 0.007 268.5)Material
--glass-tintThe colour of the pane itself.oklch(1 0 0)--glass-opacityHow opaque a default pane is.52%--glass-opacity-strongDialogs, menus, popovers, the header.74%--glass-opacity-subtleCode blocks, previews, quiet panes.28%--glass-blurHow much the backdrop is blurred.28px--glass-saturateHow vivid the colour behind stays.180%--glass-brightnessLighten or darken what shows through.100%--glass-sheenA specular gradient across the pane.10%--glass-sheen-angleWhere the light comes from.160deg--glass-textureA noise texture laid over the frost.none--glass-fill-tintInputs, tracks, idle pills on glass.oklch(0.55 0.01 285)--glass-fill-opacityHow strong control fills are.12%--glass-fill-opacity-strongHovered and pressed fills, tracks.20%--glass-thumbThe raised thumb of segmented controls.oklch(1 0 0)Rim
--glass-border-colorThe edge around every pane.oklch(1 0 0)--glass-border-opacityHow visible the edge is.85%--glass-border-widthEdge thickness.1px--glass-highlight-opacityThe inset top edge catching light.92%Depth
--glass-shadow-colorColour of the drop shadow.oklch(0.3 0.05 270)--glass-shadow-opacityDrop shadow strength.12%--glass-shadow-yHow far below the pane the shadow falls.10px--glass-shadow-blurHow soft the shadow is.30px--glass-glowAn accent glow inside every pane.0%Shape
--glass-radius-surfaceCards, dialogs, sheets, panes.1.5rem--glass-radius-controlInputs, menus, segmented controls.0.75rem--glass-radius-buttonButtons, tabs, the dock. 9999px is a capsule.capsule--glass-radius-badgeBadges and tags.capsule--glass-ring-widthKeyboard focus ring thickness.3pxDensity
--glass-densityScales control heights, paddings and card spacing.1Motion
--glass-durationBase transition length.200ms--glass-easeThe curve every transition follows.spring--glass-press-scaleHow far buttons squash when pressed.0.97Aurora
--aurora-opacityOpacity of the blobs.100%--aurora-scaleBlob size multiplier.1--aurora-speedDrift speed multiplier.1--aurora-blurExtra blur on the blobs.0pxFonts
--glass-font-sansText on glass. Unset: your app's --font-sans.app font--glass-font-headingTitles and headings. Unset: your app's --font-heading.app font--glass-font-displayBig figures: stats, rings, Display. Unset: the heading font.app font--glass-font-monoCode and keycaps. Unset: your app's --font-mono.app fontType
--glass-text-scaleMultiplies every step of the type scale (1 = 16px).1--glass-type-ratioHow fast headings grow, step to step.major third--glass-leadingBody text line height.1.6--glass-trackingBody text letter spacing.0em--glass-body-weightBody text weight.400--glass-heading-weightCard, dialog and sheet titles; headings.650--glass-heading-trackingLetter spacing of titles and headings.-0.015em--glass-heading-leadingLine height of titles and headings.1.15--glass-heading-caseText transform for titles and headings.as written--glass-heading-wrapBalance lines, avoid orphans, or wrap normally.balance--glass-display-weightBig figures: stats, rings, Display.700--glass-display-trackingLetter spacing of big figures.-0.03em--glass-numericFigures in stats, rings and tables.tabular--glass-font-featuresfont-feature-settings for body and headings.default--glass-measureMax line length of long-form text (Prose, Text lead).68ch--glass-underline-offsetLink underline distance.0.22emUtilities
glass, glass-strong, glass-subtle for surfaces; colours bg-glass, bg-fill, border-glass-border, bg-aurora-1…; corners rounded-surface, rounded-control, rounded-button, rounded-badge; sizing h-control, px-pad; motion ease-glass; type type-glass-heading, type-step-* (see Fonts & type). Two hooks replace whole layers: --glass-bg and --glass-elevation. No backdrop-filter? Panes go nearly opaque. prefers-reduced-motion stills the aurora and the charts.
:root {
--aurora-1: oklch(0.829 0.098 37.8);
--aurora-2: oklch(0.809 0.109 296.4);
--aurora-3: oklch(0.907 0.07 70.2);
--aurora-base: oklch(0.955 0.007 268.5);
--glass-tint: oklch(1 0 0);
--glass-opacity: 52%;
--glass-opacity-strong: 74%;
--glass-opacity-subtle: 28%;
--glass-blur: 28px;
--glass-saturate: 180%;
--glass-brightness: 100%;
--glass-sheen: 10%;
--glass-sheen-angle: 160deg;
--glass-texture: none;
--glass-fill-tint: oklch(0.55 0.01 285);
--glass-fill-opacity: 12%;
--glass-fill-opacity-strong: 20%;
--glass-thumb: oklch(1 0 0);
--glass-border-color: oklch(1 0 0);
--glass-border-opacity: 85%;
--glass-border-width: 1px;
--glass-highlight-opacity: 92%;
--glass-shadow-color: oklch(0.3 0.05 270);
--glass-shadow-opacity: 12%;
--glass-shadow-y: 10px;
--glass-shadow-blur: 30px;
--glass-glow: 0%;
--glass-radius-surface: 1.5rem;
--glass-radius-control: 0.75rem;
--glass-radius-button: 9999px;
--glass-radius-badge: 9999px;
--glass-density: 1;
--glass-duration: 200ms;
--glass-ease: cubic-bezier(0.2, 0.9, 0.3, 1.12);
--glass-press-scale: 0.97;
--aurora-opacity: 100%;
--aurora-scale: 1;
--aurora-speed: 1;
--aurora-blur: 0px;
--glass-ring-width: 3px;
--glass-text-scale: 1;
--glass-type-ratio: 1.25;
--glass-leading: 1.6;
--glass-tracking: 0em;
--glass-body-weight: 400;
--glass-heading-weight: 650;
--glass-heading-tracking: -0.015em;
--glass-heading-leading: 1.15;
--glass-heading-case: none;
--glass-heading-wrap: balance;
--glass-display-weight: 700;
--glass-display-tracking: -0.03em;
--glass-numeric: tabular-nums;
--glass-font-features: normal;
--glass-measure: 68ch;
--glass-underline-offset: 0.22em;
}
.dark {
--aurora-1: oklch(0.48 0.132 31.7);
--aurora-2: oklch(0.401 0.159 289.2);
--aurora-3: oklch(0.456 0.088 60.9);
--aurora-base: oklch(0.116 0.006 285.4);
--glass-tint: oklch(0.24 0.012 285);
--glass-opacity: 45%;
--glass-opacity-strong: 72%;
--glass-opacity-subtle: 24%;
--glass-blur: 28px;
--glass-saturate: 180%;
--glass-brightness: 100%;
--glass-sheen: 5%;
--glass-sheen-angle: 160deg;
--glass-texture: none;
--glass-fill-tint: oklch(0.6 0.01 285);
--glass-fill-opacity: 24%;
--glass-fill-opacity-strong: 36%;
--glass-thumb: oklch(1 0 0 / 20%);
--glass-border-color: oklch(1 0 0);
--glass-border-opacity: 12%;
--glass-border-width: 1px;
--glass-highlight-opacity: 14%;
--glass-shadow-color: oklch(0 0 0);
--glass-shadow-opacity: 45%;
--glass-shadow-y: 10px;
--glass-shadow-blur: 30px;
--glass-glow: 0%;
--glass-radius-surface: 1.5rem;
--glass-radius-control: 0.75rem;
--glass-radius-button: 9999px;
--glass-radius-badge: 9999px;
--glass-density: 1;
--glass-duration: 200ms;
--glass-ease: cubic-bezier(0.2, 0.9, 0.3, 1.12);
--glass-press-scale: 0.97;
--aurora-opacity: 100%;
--aurora-scale: 1;
--aurora-speed: 1;
--aurora-blur: 0px;
--glass-ring-width: 3px;
--glass-text-scale: 1;
--glass-type-ratio: 1.25;
--glass-leading: 1.6;
--glass-tracking: 0em;
--glass-body-weight: 400;
--glass-heading-weight: 650;
--glass-heading-tracking: -0.015em;
--glass-heading-leading: 1.15;
--glass-heading-case: none;
--glass-heading-wrap: balance;
--glass-display-weight: 700;
--glass-display-tracking: -0.03em;
--glass-numeric: tabular-nums;
--glass-font-features: normal;
--glass-measure: 68ch;
--glass-underline-offset: 0.22em;
}