Implementation
One vocabulary of seven theme tokens, resolved by Client/src/theme.ts
resolveThemeTokens() into a background / foreground / accent triple. Three
surfaces consume it: container theme (block chrome), item theme
(per-card / per-panel via IsThemeable), and component auto-rotations
(index-based defaults when the editor leaves the picker empty). Rule going
forward: any themeable block composes IsThemeable and consumes
resolveThemeTokens() — never local hex.
Theme token triples
Section titled “Theme token triples”Values below are the verbatim CSS custom-property references returned by
resolveThemeTokens() in theme.ts.
| token | background | foreground | accent |
|---|---|---|---|
neutral |
var(--color-neutral-200) |
var(--color-neutral-900) |
var(--color-neutral-700) |
white |
var(--color-white) |
var(--color-neutral-900) |
var(--color-egyptian-blue) |
egyptian-blue |
var(--color-egyptian-blue) |
var(--color-white) |
var(--color-egyptian-blue-accent) |
blush-pop |
var(--color-blush-pop) |
var(--color-neutral-900) |
var(--color-blush-pop-accent) |
ash-grey |
var(--color-ash-grey) |
var(--color-neutral-900) |
var(--color-ash-grey-accent) |
sand-dune |
var(--color-sand-dune) |
var(--color-neutral-900) |
var(--color-sand-dune-accent) |
granite |
var(--color-granite) |
var(--color-white) |
var(--color-granite-accent) |
Dark panels are those whose foreground is white: egyptian-blue,
granite. Everything else is a tint panel (dark ink on a light ground).
Three theming surfaces
Section titled “Three theming surfaces”| surface | composition / owner | property | when empty | consumers |
|---|---|---|---|---|
| container | Humble_UI_HasContainer |
containerTheme |
Transparent container — page ground shows through | Video, Accordion, Rich Text, Statement Panels (block chrome), Column Layout, … — any block that wraps content in HumbleContainer |
| item | Humble_Web_Compositions_IsThemeable |
theme (Contentment Data List: Container Theme - Data List) |
Component auto-rotation for that index (see below); Stats pass through null and the component defaults its surface | Humble_UI_Stat, Humble_UI_VerticalCard, Humble_UI_StatementPanel, Humble_UI_BlockSettings |
| auto-rotation | Component / Razor default (not a composition) | Passed as palette or themes prop arrays |
n/a — this is the empty-case default | Vertical cards, Statement panels (see table below) |
Component auto-rotations
Section titled “Component auto-rotations”When every item leaves theme empty, Razor builds the full sequence from these
defaults so the Svelte island receives an explicit array and never invents
palette policy of its own (the component still keeps a matching DEFAULT_* for
Delivery-shaped fallback paths). With theme unset everywhere, the rendered
result is the full default rotation.
| component | prop | default order (repeating) | Razor |
|---|---|---|---|
| Vertical cards | palette |
egyptian-blue → blush-pop → ash-grey |
Humble_UI_VerticalCards.cshtml / …VerticalCard.cshtml |
| Statement panels | themes |
white → egyptian-blue → ash-grey |
Humble_UI_StatementPanels.cshtml |
// Per item, after the filtered list is built:token = item.theme is set and non-empty ? item.theme : rotation[index % rotation.Length]
// Vertical cards → palette[]; statement panels → themes[]// Island derives all visual treatment from resolveThemeTokens(token)Contrast
Section titled “Contrast”Checked pairs documented for the vertical-cards media-panel treatment
(docs/design/vertical-cards.html, Concept A2). Statement panels and stats use
the same triples for full-surface colour, so the same foreground/accent
pairings apply.
| token | kind | checked pair | approx. contrast |
|---|---|---|---|
egyptian-blue |
dark panel | white on egyptian | ≈ 10.5:1 |
blush-pop |
tint panel | deep magenta (--color-blush-pop-accent) on blush |
≈ 4.6:1 |
ash-grey |
tint panel | deep teal (--color-ash-grey-accent) on ash |
≈ 6.5:1 |
Derivation rule that keeps other tokens safe. Components never pick
mark/label/heading colours by token name. They call resolveThemeTokens(name)
and branch only on foregroundColor === "var(--color-white)":
- Dark panel (egyptian-blue, granite): white mark; glow mixed from the
theme accent; label lightened via
color-mixof accent into white. White-on-dark stays high-contrast by construction. - Tint panel (white, neutral, blush-pop, ash-grey, sand-dune): the theme
accent is the mark/label/heading colour over the theme background (or a
white glow). Accents in
theme.tsare the dark companion of each tint, so ink-on-tint contrast is the authored pair — not an ad-hoc mix.
Adding a token therefore means: (1) define background + foreground + accent in
theme.ts / colour tokens with a checked accent-on-background pair, (2) add
the value to the Contentment data list, (3) do not invent component-local
hex. Dark vs tint behaviour falls out of the foreground choice.
Implementation map
Section titled “Implementation map”| piece | where | notes |
|---|---|---|
| Resolver | Client/src/theme.ts |
normalizeThemeToken · resolveThemeBackground/Foreground/Accent · resolveThemeTokens |
| Colour tokens | Client/src/design-system/tokens/colors.nri.css |
Brand + semantic hex; accents used by theme triples |
| Item composition | Humble_Web_Compositions_IsThemeable (uSync key 704e32c1-…) |
Optional theme property; data type Container Theme - Data List (36ef9af5-…, Contentment) |
| Container composition | Humble_UI_HasContainer |
containerSize · containerWhitespace · containerTheme — same data list for the theme slot |
| Vertical cards | Razor maps palette[]; Svelte humble-vertical-cards.svelte |
Card surface stays white; media panel rotates via palette. See docs/design/vertical-cards.html |
| Statement panels | Razor maps themes[]; Svelte humble-statement-panels.svelte |
Full-panel background from the triple. See docs/design/statement-panel.html |
| Stats | Humble_UI_Stat + humble-stats.svelte |
Per-stat theme from the composition (typed stat.Theme / Value); no auto-rotation |
The rule
Section titled “The rule”Any themeable block composes Humble_Web_Compositions_IsThemeable (or
HasContainer for block chrome) and consumes resolveThemeTokens() — never
component-local hex. New tokens land in theme.ts and the Contentment data list
first; components only branch on the derived dark/tint fact.
Companion specs (repo living HTML): docs/design/vertical-cards.html,
docs/design/statement-panel.html, docs/design/type-roles.html. Source of
truth for token resolution is src/NRI.CMS/Client/src/theme.ts; update the
canonical docs/design/theming.html and this page in the same commit when the
vocabulary or surfaces change.