glasscn
Guides

Theming

68 primitives in 10 groups. Pick presets, turn any knob, theme a single card — all with CSS variables.

Theme Studio

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.

Open the studio

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:

any element
<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.json

Palettes

Dusk
theme-dusk
Ocean
theme-ocean
Rose
theme-rose
Sage
theme-sage
Amber
theme-amber
Graphite
theme-graphite
Lavender
theme-lavender
Mint
theme-mint
Cherry
theme-cherry
Lagoon
theme-lagoon
Sand
theme-sand
Midnight
theme-midnight

Materials

Frostedmaterial-frosted
Soft frost, a light rim — the default.
Liquidmaterial-liquid
Clear and vivid: little frost, strong saturation, a bright sheen.
Crystalmaterial-crystal
Little blur, a crisp double-strength rim and a hard highlight.
Smokedmaterial-smoked
Dark tinted glass in both schemes.
Mattematerial-matte
No blur at all — opaque panes. The fastest option on slow devices.
Vapormaterial-vapor
Heavy, misty blur with almost no edge.
Neonmaterial-neon
Dark frost lit from inside by the accent.

Shapes

Roundshape-round
Capsule buttons, generous corners — the default.
Softshape-soft
Rounded rectangles everywhere.
Sharpshape-sharp
Tight corners, still not square.
Squareshape-square
No rounding at all.

Motion

Springmotion-spring
A little overshoot — the default.
Smoothmotion-smooth
Material-style easing, no overshoot.
Snappymotion-snappy
Quick with a bounce.
Stillmotion-still
No transitions, no press squash, a frozen aurora.

Density

Compactdensity-compact
×0.85
Defaultdensity-default
×1
Comfortabledensity-comfortable
×1.15

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.json
theme.ts
import { 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.3px

Density

--glass-densityScales control heights, paddings and card spacing.1

Motion

--glass-durationBase transition length.200ms
--glass-easeThe curve every transition follows.spring
--glass-press-scaleHow far buttons squash when pressed.0.97

Aurora

--aurora-opacityOpacity of the blobs.100%
--aurora-scaleBlob size multiplier.1
--aurora-speedDrift speed multiplier.1
--aurora-blurExtra blur on the blobs.0px

Fonts

--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 font

Type

--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.22em

Utilities

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.

what glass-style adds to globals.css
: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;
}