Skip to main content
Urbicon UI

Color Rooms

A token-only overlay on top of the Urbicon UI library that drives the look of this docs site — Schibsted Grotesk on warm cream paper, with the accent set to the room (section) you are in. Activated via a single root class; everything else is CSS custom properties.

What it is

Color Rooms is the visual identity of this docs site — one grotesk (Schibsted Grotesk) for both display and body, JetBrains Mono for meta and code, on warm cream paper. Its signature is that the accent is the room you are in: each product area owns a saturated colour, and the component-page and section-landing headers become a full-width colour field that spans everything right of the app sidebar. It is implemented as a single CSS file (apps/docs/src/lib/style/rooms-docs.css) that defines a private --docs-* token namespace and re-derives the library's primary-token family from the active room when .docs-rooms is present on the <html> root.

Nothing in the library has been forked or duplicated. Every primitive still ships with its library-default look; Color Rooms is a thin token-override sheet over the top. Without .docs-rooms the same page renders in the library defaults — that's the test of whether the docs site really is theme-only. The Docs theme toggle in the sidebar footer (Rooms / Library) removes the class live to prove it.

Color Rooms vs. Consumer Apps

Color Rooms is docs-only. Consumer apps that depend on @urbicon-ui/blocks do not import rooms-docs.css and do not set .docs-rooms; they live on the library defaults and customise via the regular customization ladder (class / slotClasses / preset / defaults / overrides / unstyled).

The Rooms

Four rooms, one per product area. The top-level route segment picks the room; everything outside the four areas falls back to the blocks green. The two custom properties (--room-accent and its foreground --room-accent-fg) are fed in from the layout onto a .docs-room-scope wrapper, and the whole primary-token family is re-derived from them via color-mix(). Switching route therefore repaints every real component on the page — segment indicator, slider, toggle, buttons, TOC-active, badges — with no per-component override.

RoomRouteAccent · Foreground
Blocks (default)/blocks/**#00845c · #f6f3ec — green
Table/table/**#7c1f2d · #f6f3ec — wine
Auth/auth/**#e3a31c · #17150f — amber
AI & DX/ai/**#e8500f · #17150f — orange

Route → room

Loading...
Loading syntax highlighting...

The derivation is re-declared in two scopes on purpose. Content reads the room from the .docs-room-scope wrapper — SSR-correct, so the first paint already carries the right accent with no flash. Portaled popovers (the Select / Combobox / Menu dropdowns) mount at <body>, outside that wrapper, so the same accent is mirrored onto <html> after mount — they only open on interaction, always post-hydration, so there is no first-paint concern for them.

color-mix derivation

Loading...
Loading syntax highlighting...

A scoped theme has to re-declare its derived semantic tokens: a var() inside a :root token definition substitutes at the cascade level where it is defined, so overriding the ramp alone won't re-resolve --color-primary & co. — see Tier System.

Token Catalogue

The --docs-* namespace holds the paper/ink hierarchy; the primary family is derived from the room (above). Color Rooms also binds a handful of library semantic tokens to the --docs-* values so components rendered inside .docs-rooms pick up the warm palette without per-component opt-in.

Docs-private tokens

TokenDefault (light · dark)Used for
--room-accentper sectionThe active room colour — source of the whole primary family.
--room-accent-fgper sectionOn-accent ink/cream — text on the field + on primary fills.
--docs-bg#f7f5f0 · #1a1816Ground — the page surface outside content blocks.
--docs-paper#fbfaf6 · #232220Content surface — where library components sit.
--docs-ink#17150f · #f0ede5Primary text ink.
--docs-soft#6e6b64 · #a5a299Body-soft / meta ink (ON THIS PAGE labels, descriptions).
--docs-softer#b8b5ad · #5a574fDecoration ink — kicker separators.
--docs-hairink/8% · cream/8%Hairline — barely-visible structural lines.
--docs-accentvar(--color-primary)Link colour, section markers — couples to the room primary.
--docs-radius-cardvar(--radius-contain)Bento-cards, recipe-stages — tight for the hard-edge poster.
--docs-shadow-pagevar(--blocks-shadow-lg)Lift shadow — bento, recipe, showcase stages.
--font-display / --font-sansSchibsted Grotesk VariableDisplay + body — one grotesk (self-hosted via @fontsource).
--font-monoJetBrains Mono · system monoCode, kbd, meta kickers (self-hosted via @fontsource).

Library semantic overrides

Color Rooms rebinds these library semantic tokens inside .docs-rooms so library components automatically pick up the warm palette and the room accent. The surface ladder, warm-neutral border/state ramp and warm-tuned intent colours (secondary/success/warning/danger) are re-pointed too so the whole page reads warm rather than cool.

Library tokenColor Rooms value
--color-primaryvar(--room-accent)
--color-text-on-primaryvar(--room-accent-fg)
--color-surface-basevar(--docs-paper)
--color-surface-quietvar(--docs-bg)
--color-border-hairlinevar(--docs-hair)
--color-text-primaryvar(--docs-ink)
--color-text-secondaryvar(--docs-soft)

Activation

The theme activates when .docs-rooms sits on a parent of your content. The docs site puts it on <html> as the shipped default so the whole app inherits the warm canvas — and so the app.html head script can flip it before first paint (the root element exists there, <body> does not yet). The per-route room accent then lives on the .docs-room-scope wrapper in the layout.

1. Set the root class

Loading...
Loading syntax highlighting...

And import the stylesheet alongside the library base:

2. Import rooms-docs.css

Loading...
Loading syntax highlighting...

Remove the class (the sidebar's Docs theme → Library toggle does exactly this) and the page falls back to the bare library skin — no field header, no Schibsted, the library's blue primary. The underlying override mechanism is the same CSS-custom-property cascade documented in Tier System — Color Rooms just rebinds a wider set of tokens and derives them from the room.

Light & Dark

Color Rooms supports both light and dark out of the box — Light is warm cream paper with warm dark ink, Dark is warm coffee paper with warm cream ink. The room accent is orthogonal to the mode: it repaints the primary family, not the paper, so a section stays the same colour in both modes and the docs' ThemeSwitcher keeps working.

Implementation uses CSS's light-dark() function. Each paper/ink token resolves at use-site based on the page's color-scheme, which the library's semantic layer sets via :root.light / :root.dark. A single rule block covers both modes — no second sheet, no JS-driven swap.

Light/Dark via light-dark()

Loading...
Loading syntax highlighting...

The same mechanism powers semantic.css — see Design Tokens for the library-side details.

Override Recipes

Because every value lives on a CSS custom property, you can adjust the theme at any scope — globally inside .docs-rooms, per-room on .docs-room-scope, or inline. A few common adjustments:

Common overrides

Loading...
Loading syntax highlighting...

For brand-wide theming (the library's own primary colour, the tier radii) reach for the Theme Builder or write a custom @theme block — Color Rooms sits on top of that and, per room, overrides the primary chain with the room accent.