Sistine

Theming

Recolor and restyle the glass with a few CSS hooks. They ship in @sistine/theme (and are embedded in every component), so they work the moment you install anything: these are the same knobs the demo's header switchers drive.

Mental model
Every glass surface is one material you pick, plus orthogonal axes you switch on.
  • Material (the substance): glass · frosted · crystal · chakra · opaque. Set it with the material prop, or leave it off for adaptive glass, the default, which follows the page's data-glass style. Crystal has three flavors: data-gloss white / tonal / hue (see below).
  • Axes (orthogonal, so they compose freely on any material): border, veil (a legible floor for menus / overlays), diffuse (a readability blur floor), gradient (brand wash), glow, sheen (hover shimmer). Each is a prop on a component, or a glass-* class on a bare element.

The five materials. Pin one with the prop, or on a raw element with the attribute:

materialpropraw elementlook
glassmaterial="glass" (or default)data-material="glass"sheer adaptive glass
frostedmaterial="frosted"data-material="frosted"milky, heavier blur
crystalmaterial="crystal"data-material="crystal"bright specular gloss
chakramaterial="chakra"data-material="chakra"step-cut gem: all four edges faceted
opaquematerial="opaque"data-material="opaque"fully solid, no see-through

The axes stack on top of whatever material you chose:

axispropclasswhat
borderborder / "rim" / "frame"glass-border / -rim / -framematerial edge: hairline 1px (0.5px under frosted) · rim 2px · frame 4px
veilveilglass-veilalpha floor for read-through overlays: the default readability contract on menus, tooltips, toasts
diffusediffuse / "stained"glass-diffuse / -stainedblur floor (≥ 12px) for text-dense translucency; stained dyes the backdrop in the theme hue
gradientgradientglass-gradientbrand-gradient accent wash
glowglow / glow="lg"glass-glow / -lgthemed halo that tracks the tint hue
sheensheenglass-sheenopt-in hover shimmer

An explicit material pins that element under any page style. And because materials inherit, it also re-skins the adaptive glass nested inside it (a frosted dialog re-skins its own controls; pin a child back with its own material).

Glass tint: data-glass-tint
Recolor all glass in a subtree. Works on any element, not just <html>.

Set data-glass-tint to a preset: on <html> for the whole app, or on any wrapper to tint just that section (it re-resolves per scope):

<html data-glass-tint="sapphire">        <!-- whole app -->

<section data-glass-tint="sistine">      <!-- just this panel -->
  <Card material="crystal">…</Card>
</section>

Presets

Jewels (single hue): selenite, rose, goldstone, carnelian, amber, moonstone, peridot, aventurine, turquoise, aquamarine, sapphire, lapis, amethyst, tourmaline.

Frescoes (multi-hue gradients): sistine, muse, aurora, gloaming.

Status: info, success, warning, destructive. Used by Alert/Button, but settable on any element to tint it that status (e.g. data-glass-tint="info").

Custom color: OKLCH H · C · L

For an arbitrary color, set the tint vars directly instead of a preset. The model is straight OKLCH: hue + chroma (the single “how colorful” dial: chroma 0 = neutral) + lightness (lower = deeper).

<div
  style={{
    "--glass-tint-h": 280,     /* hue 0–360 */
    "--glass-tint-c": 0.07,    /* chroma 0–~0.2 — the colorfulness master */
    "--glass-opaque-l": 40,    /* tint body lightness — lower = deep (e.g. deep purple) */
  }}
>
Glass material: the material prop & data-glass
Switch the material: glass, frosted, crystal, chakra, opaque.

Per component, pin a material with the material prop (on a bare element, use the data-material attribute):

<Card material="frosted">…</Card>
<Button material="crystal">…</Button>

<div className="glass glass-border" data-material="crystal">…</div>

Globally (or per subtree), set data-glass to re-skin every adaptive surface inside, the ones that left material off. A pinned material ignores it:

<html data-glass="frosted">              <!-- all adaptive glass = frosted -->
<section data-glass="crystal">…</section>  <!-- scoped -->
Crystal flavors: data-gloss
Swap the crystal gloss: a white specular, a theme tint, or an iridescent hue sweep.

Crystal's shine has three flavors, set with data-gloss on any ancestor (default / unset = tonal). It composes with material="crystal" and the data-glass="crystal" page style:

data-glossgloss
whiteflat white specular: the classic glass shine
tonala tonally-close tint of the theme (pearlescent): the default
hueiridescent: the highlight sweeps hues shifted ± around the tint
<html data-gloss="hue">                      <!-- iridescent crystal everywhere -->
<section data-gloss="white">…</section>     <!-- classic specular, scoped -->

Tune the gloss with these tokens: all have safe defaults, so override only what you want:

tokendefaultwhat
--glass-gloss-l66highlight lightness (→ %); lower = bolder
--glass-gloss-tint4.25tonal: × the theme chroma (0 = white)
--glass-gloss-hue-span40hue: ° the sweep shifts ± around the tint
--glass-gloss-c0.16hue: chroma of the swept stops
--glass-gloss-hue-dir1direction (±1): tonal streak / hue order

Play with every flavor and knob live on the Colors page.

Surfaces on raw elements
Build a glass surface by hand: the structural class plus the axis classes.
  • glass is the structural surface: sheerest, borderless. Everything else layers on top.
  • glass-border: adds the material edge (a hairline; pair with glass-sm / glass-lg for the blur / elevation tier).
  • glass-veil: a mostly-opaque, legible floor for read-through overlays (menus / tooltips).
  • material="opaque" (or data-material="opaque"): fully solid, no see-through.

On a component these are the border / veil and material props; on a bare element, apply the classes and the data-material attribute directly:

<div className="glass glass-border glass-veil rounded-xl p-4">Legible overlay</div>
Readable text & contrast
Legible foreground without the harsh pure-black / pure-white spike: APCA contrast, banded to the ARC “Bronze Simple Mode” criterion.

Contrast is a band, not a maximum: enough to read (floor), capped so it doesn't glare (ceiling). Each role maps to an APCA Lc band, and it all routes through readableForeground(), which aims for the band's target instead of maxing out.

Size tiers: the easy path

Three foreground utilities, computed once by AutoForeground and tinted with the theme. Swap the class by text size: no JS per element:

utilitybandfor
text-foreground~bodybody text (default)
text-foreground-softLc ~58headings / large: eased off the spike
text-foreground-strongLc ~90fine / small print
<h1 className="text-foreground-soft">Heading</h1>
<p className="text-foreground">Body copy…</p>
<small className="text-foreground-strong">Fine print</small>
<GearIcon className="text-foreground-ui" />   {/* standalone icon */}

Icons

Icons are non-text, so the rule is Lc ≥ 45 (the ui band, the APCA analog of WCAG's 3:1). A labeled icon just inherits currentColor; a standalone icon gets text-foreground-ui: the shipped icon foreground (ui band, target Lc 60) with an optional hue you can cycle from the Colors tester. Stroke weight is the dial: thin outlines need more contrast, bold / filled glyphs can go softer (Phosphor's weight prop).

Off-theme surfaces

For a surface that isn't the theme (a colored tool-call bubble, a status pill), band against its color. The hook is pure (no DOM read, memoized), so it scales to hundreds of them:

const bubble = { l: 70, c: 0.18, h: 50 };          // your orange (oklch)
const text = useReadableForeground(bubble, "body");
const icon = useReadableForeground(bubble, "ui");  // Lc ≥ 45

Or, for the accent-guard on a custom surface:

<ReadableText accent="--primary" on="oklch(70% 0.18 50)" usage="body">
  Brand color when legible, soft fallback when not
</ReadableText>

Usage bands

The usage preset picks the band. Floors are spec-fed (ARC Bronze / WCAG); targets & ceilings are tuned margins. Aim small, go softer as text gets larger:

usagefloorfor
small90fine / thin text
body75body (default)
large45large text / headings
ui45icons, controls, focus rings
non-text30borders, dividers
disabled30placeholder / disabled

Complementary accents

complement() / harmony() derive accent hues off the theme color (oklch hue rotation, a balanced opposite, not the skewed HSL one); pair with the hook for readable text on them:

const accent   = complement({ l: 60, c: 0.15, h: themeHue });  // opposite hue
const onAccent = useReadableForeground(accent, "large");
// harmony(base, [120, 240]) → triadic · [-30, 30] → analogous

Which to reach for

text on…usecost
the theme surface, in bulktext-foreground / -soft / -strongzero JS / element
an accent that must stay legibleReadableText1 effect + observer / instance
an off-theme surface (many)useReadableForegroundpure memo, no DOM
Harmonic color tokens
Color-wheel relationships derived from the theme hue. Pure CSS, no JS.

Every relationship off the harmony anchor (--harmony-h: the content hue, or 0 for the hue-less selenite / moonstone themes) ships as a token, so they rotate with the tint automatically. The hue tokens are angles: use anywhere as oklch(L C var(--hue-*)):

familywhat
--hue-*angle tokens: base, complement, analogous-1/2, split-1/2, triad-1/2, tetrad-1/2/3, square-1/2/3
--color-*ready vivid oklch colors at the accent envelope (complement, triad-1/2, split-1/2, analogous-1/2)
--mono-1..3a monochromatic ramp in the theme hue
<div style={{ background: "oklch(0.6 0.16 var(--hue-triad-1))" }} />
<div style={{ background: "var(--color-complement)" }} />

Standalone icons (text-foreground-ui) can pin to any of these relationships, contrast-solved, from the Colors tester.

Foreground tier preview

Solid 65%
white (readable)baseblack (toward bg)
107
100
Fn
93
86
Bd
79
73
67
Lg
61
55
49
44
39
·
34
27
21
16
10
0
0
0
0
0
0
0
0
Large heading
Body copy: the quick brown fox jumps over the lazy dog.
Fine print: the quick brown fox jumps over the lazy dog.
icons · readableForeground(ui): Lc 60
thinlightregularboldfillgearheartstarbellsearch
tiercolorLchromaLc / band
large / heading77%0.00061 / 4576
body (default)87%0.00079 / 7590
fine / small93%0.00093 / 90100

The strip is the full Linear ramp (via themeForeground): extreme → base → extreme; each tier takes the swatch in its [floor–ceiling] band nearest target, so fine stays ≥ 90 (a floor, not a cap). Lc is modeled on the solid floor. Linear holds the theme's chroma; Tonal fades toward gray. Icons are separate: readableForeground solves lightness for the ui band at your chosen harmonic angle (complement / triad / split / tetrad / square, the same --hue-* tokens as the swatches), so they stay legible while tracking the theme.

Tuning: CSS variables
Fine dials, settable on <html> (global) or any element (scoped).
  • --glass-tint-h: tint hue (OKLCH H, 0–360).
  • --glass-tint-c: tint chroma, the single “how colorful” master. It drives surfaces, text, and the harmonic accents together. 0 = neutral.
  • --glass-opaque-l: tint body lightness (OKLCH L); lower = a deeper tint. Mode-aware default (90 light / 32 dark), and the floor AutoForeground bands opaque-card text against.
  • --glass-opacity: solidity floor for any glass element: 0 = the material's natural sheer glass, 1 = reads fully opaque. Defaults to 0.7. Set it with glassVars({ opacity }) (or inline).
  • --glass-solid-a: how solid the glass-veil floor is (≈0.25–0.75; default 0.65).
  • --glass-diffuse: the diffuse axis' blur floor (default 12px).
  • --glass-stain-amount: how completely stained strips the backdrop's color (0–1; default 1).
  • --glass-opaque-outline: optional accent outline for opaque surfaces. Unset → the plain glass border; set it (e.g. var(--glass-accent)) on any ancestor to give every opaque component a colored edge. --glass-opaque-outline-w sets its weight (the switcher's hairline / rim / frame = 1 / 2 / 4px); an element-level border="rim" / "frame" still wins.
  • --glass-tint-a: the tint film alpha (per preset).

These re-resolve per scope, so a scoped data-glass-tint or an inline var affects just that subtree:

<aside style={{ "--glass-solid-a": 0.8 }}>
  <div className="glass glass-veil …">extra-solid here only</div>
</aside>
Readability optics
Three ways a translucent surface stays legible: veil, diffuse, stained.

veil is the default readability contract: an alpha floor under the text, free of any blur cost, and how menus, tooltips, and toasts ship. diffuse is the opt-in blur floor (≥ --glass-diffuse, 12px) for text-dense surfaces that must stay translucent: crystal keeps its 2px identity until a surface opts in, and opaque ignores it. diffuse="stained" is the dyed mode: the backdrop collapses to pure luminance, and the glass's own tint supplies all the color.

Drag the floor: the veiled card holds while plain glass rides the backdrop:

veil: floor tracks the slider
The quick brown fox jumps over the lazy dog. Is this still legible?
plain glass: no floor
The quick brown fox jumps over the lazy dog. Is this still legible?
--glass-solid-a65%

Raise the blur floor: crystal stays crystal until the text needs it:

diffuse: blur floor tracks the slider
The quick brown fox jumps over the lazy dog. Is this still legible?
plain crystal: its own 2px blur
The quick brown fox jumps over the lazy dog. Is this still legible?
--glass-diffuse12px

Stain the glass: the backdrop re-renders as tonal shades of the theme:

stained: backdrop stripped to luminance
The quick brown fox jumps over the lazy dog. Is this still legible?
plain crystal: backdrop keeps its colors
The quick brown fox jumps over the lazy dog. Is this still legible?
--glass-stain-amount1.00
Backgrounds
Theme- and tint-aware wallpapers: installable, and fresco-aware.

Two background styles ship in the registry and recolor with the live tint. A fresco preset lays its full multi-hue palette across them, not just one color:

npx shadcn@latest add @sistine/canvas-background    # animated canvas (gradient / lava / circle)
npx shadcn@latest add @sistine/gradient-background  # pure-CSS gradient wallpaper

Mount one at your app root as a fixed inset-0 -z-10 element, and keep it out of any transform / filter / contain ancestor, or it gets clipped to that box instead of the viewport.

<GradientBackground> paints as a linear, radial, or conic gradient via its shape prop, with angle, position, and (radial) radialShape / radialSize geometry, e.g. <GradientBackground shape="conic" position="30% 70%" />.