Skip to content

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.

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).

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)

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-blueblush-popash-grey Humble_UI_VerticalCards.cshtml / …VerticalCard.cshtml
Statement panels themes whiteegyptian-blueash-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)

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-mix of 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.ts are 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.

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

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.