Amends the stackra brand kit’s surface rule, which said “No glass (shadows allowed), no gradients”
and “no top-line highlight”. The rule’s record is
packages/design-system/tokens/assets/data/stackra/brand.json (decided.surfaces). Amends
0302 only where it describes the
playground’s look.Context
The stackra brand kit (approved 2026-09-29) settled every surface as an opaque layer: the page ground, then section sheets, then recessed stages, with emphasis by an inverted card. It recorded “no glass, no gradients, no top-line highlight”. On 2026-10-24 the owner asked for a premium, glass showcase for the design system playground, with Stackra branding throughout, and decided to change the brand record to allow it. This ADR records the change and draws its limits, so the material does not drift into product screens or into the component library.Decision
- A glass material joins the brand. It is a translucent fill of a surface role, a backdrop blur and a hairline
edge, with a light top edge allowed. It is built from the theme’s roles only, with no colour of its own. Glass is
for showcase and marketing surfaces: the playground, the docs landing, marketing pages and the static host.
Product UI (console, mobile, panel resources) and the
@stackra/uicomponents keep the opaque layers. A future ADR decides whether glass reaches them. - Glass never sits under a component’s reference samples. The components’ soft and translucent variants are tuned
against the theme’s
background, so a page that documents a component (its variants, sizes and states) places them on a solid sheet of that colour; glass frames that sheet and never sits behind it. Composed scenes that show the system at work (the playground index’s showcase, its gallery panels) may sit on glass cards, as a product’s would; axe checks them in every material mode, so their contrast holds. - Gradients are allowed only as soft ambient tints, never as fills of a control or of text. Each tint is a flat colour shaped by a mask. A contrast checker (axe) then measures text against every tint at full strength at once. That is a stricter background than any point of the page shows, so the check gives a result instead of reporting “incomplete”. The tints stay light enough that muted text passes AA in that worst case.
- The fallbacks are part of the material, and its contrast is measured.
- The glass turns solid (its surface role, no blur) under
prefers-reduced-transparency, underprefers-contrast: more, and wherebackdrop-filteris unsupported. - Where
color-mix()is unsupported it is a static, opaque material: the surface, no filter, the border role as its edge. A static declaration written before a computed one cannot do this, because a declaration that holdsvar()always parses, so the fallback is an@supportsblock after the colour blocks. - It keeps a visible edge in forced colours.
- Under reduced motion it does not animate.
- The page root (
htmlandbody) stays opaque, so the material always blurs a painted page. - Reading text holds 4.5:1 over the worst backdrops. On the
regularandthicklevels, the theme’sforegroundholds 4.5:1 over pure black and over pure white, in every mode and colour scheme, with liquid’s sheen at full strength.stackra-theme.constant.test.tscomputes it through 8-bit sRGB, as a browser composites. Thethinlevel carries no reading text. - Muted text holds 4.5:1 too, on the
thicklevel only (owner decision 2026-10-25, STA-486).- On
regularit cannot: over pure black on the light scheme it reaches 3.70:1 in glass and 2.36:1 in liquid. So muted text never sits onthinorregular. - Where the
thicklevel’s own fill cannot hold it either (liquid, 3.15:1), the mode’sthickFloormakes that level solid. The other levels keep the mode’s look. - The same test computes it, and axe still checks muted text where it sits, in every mode, scheme and direction.
- On
- The glass turns solid (its surface role, no blur) under
- The marks are unchanged.
- The beam-S wordmark stays static, in its artwork colour (steel-deep on light, steel on dark).
- It may be drawn large and echoed as a hairline outline on a showcase stage.
- It is never glass, never a gradient, and never animated.
- Steel still fills no UI role, and the amber bar stays inside the “by figentra_” signature.
- Patterns and illustration.
- The brand’s grid, graph and dots patterns may sit under glass, in hairline roles, and never carry meaning.
- The brand icons (square caps, mitred joins, 24px grid) may wear a glass tile on a showcase stage.
- The material is a theme token with three levels and four modes (added 2026-10-24, STA-424).
- Levels (
MaterialLevel) are thicknesses:thinis for small chrome without reading text (tiles, chips over art).regular(.material) is for bars, cards and panels whose text is allforeground.thickis for surfaces that hold muted text or long reading text (sheets, palettes, cards with a description).- A theme gives each level a fill share of
surfaceper colour mode, a px blur and a saturation factor, plus the edge and rim-light shares (Theme.materials, validated bydefineTheme).
- Modes (
MaterialMode) are degrees, set once on<html data-material>. Every level scales by the mode’s factors (MATERIAL_MODES), so one switch moves the whole page:solidis opaque. It is the product default, and also what reduced transparency, more contrast and a missingbackdrop-filterforce.frostedgives a dense fill and a heavy blur.glassis the theme as written, and the showcase default.liquidgives a thinner fill and a lifted backdrop (more saturation, a little brightness) with a sheen along the top inside edge. Itsthicklevel is solid, so muted text keeps 4.5:1 (decision 4).
- Where each default comes from.
glassis the stylesheet’s:rootdefault, so a page with nodata-material(a static showcase page, the docs landing) gets the theme as written.DesignSystemProvidersetssolidunless it is given another mode (material), so product UI is opaque without asking, and a showcase asks for glass. Solid also comes from the fallbacks of decision 4. - The sheen is the rim light spread down. Its white is the mode’s
sheentimes the colour scheme’s rim-light share (materials.highlight): about 22% on the stackra light scheme and under 3% on its dark one, where more white would wash out light text. - Liquid draws no refraction. A displacement filter costs a filter pass per frame, works in Chromium only,
and warps text behind it. The lens is suggested by the lifted backdrop, the rim light and the sheen, all drawn
with backdrop filters and inset shadows. There are no pseudo-elements and no gradient images, so contrast
checkers can still measure text on the material.
- Rejected: an SVG displacement filter through
backdrop-filter: url(), a WebGL or canvas lens, and pre-rendered refraction images. - Revisit when WebKit bug 245510, Mozilla bug 1961378 or CSS Working Group issue 12316 is resolved.
- Rejected: an SVG displacement filter through
- Rendering.
renderWebCsswrites the material:- the shares, fills and filters go in the colour blocks, so a subtree with its own
data-themecomputes them again; - the mode factors go on
[data-material]; - the fallbacks come after the modes;
- the classes go in the one components layer.
- the shares, fills and filters go in the colour blocks, so a subtree with its own
- Native uses the platform’s own material: Liquid Glass on iOS 26 and later, solid everywhere else (older iOS
and Android). It reads
Theme.materialsandMATERIAL_MODESonly to choose between the two; the web’s CSS material is not drawn on native.
- Levels (
Consequences
-
brand.jsondecided.surfacesnow cites this ADR. The kit’s own--check(outside this repository) still asserts the old rule and must be updated when the kit is next regenerated. -
The playground (
apps/playground, STA-422) is the first surface. It uses the.material*classes, offers the four modes in its display switcher (?material=) and keeps only its ambient field insrc/styles/app.css. -
@stackra/tokenscarries the material (decision 7). A theme withoutmaterialsno longer validates, and every product theme adds the block. -
The Theme Studio (STA-423,
/theme-studio) derives a theme from a few controls and previews it, with its contrast checked against AA. It exports from the one theme through@stackra/tokens:- the web CSS (
renderWebCss, HeroUI v3) and native CSS (renderNativeCss, Uniwind); - the full JSON (
stackra.theme/1: the theme, with HeroUI’s own custom properties and Stackra’s additions); - a changes-only JSON (
stackra.theme-diff/1); - W3C DTCG tokens.
parseThemeJson. - the web CSS (
Enforcement
stackra-theme.constant.test.ts: the worst-case contrast of decision 4.render-web-css.function.test.ts: the levels, modes, sheen and fallbacks are written, the fallbacks after the blocks they override;web.cssis its file snapshot.- The playground’s end-to-end suite: axe on the index in every mode, scheme and direction over an opaque root; the glass solid under more contrast and under reduced transparency (emulated through the DevTools protocol); an edge in forced colours.
apps/playground/src/styles/__tests__/app.test.ts: the playground never loads HeroUI Pro’s glass theme.- Planned: a design-brand review rule that refuses
backdrop-filter, or a gradient fill, outside the surfaces of decision 1. Until then, review applies it.