utwordSystem v3.25.7
Foundations

Color

Two grounds whose roles swap. The colour that is the page in one mode is the ink in the other, so contrast is symmetric and one rule set covers both.

stable

How it works

Outword has a dark ground (#202845, the default) and a light ground (#F2F0EA), and the roles swap between them: the ink of one is the page of the other. Two semantic hues, a green and a rose, carry meaning and are the only colours that don't invert. One more hue, a blue, is reserved for the focus ring.

The token layer has three tiers:

  1. Base values: --dk-* and --lt-*, the real hex and rgba values, one per role per mode. These are the only literals in the system.
  2. Active roles: --ground, --fg, --tile… Components use these and nothing else. By default each points at its --dk-* value.
  3. Mode swap: :root[data-mode="light"] points every active role at its --lt-* value. Flipping that one attribute is the entire light/dark mechanism.

Tokens

TokenDarkLightRole
--ground#202845#F2F0EAthe page
--raisergba(242,240,234,.08)#FAF9F6anything sitting on the page (cards, fields, rows)
--raise-solid#313852#FAF9F6opaque twin of --raise, for the crown's halo where a translucent fill can't cover what's behind it
--fg#F2F0EA#202845primary text / primary fill
--fg-soft#A5ABC4#5F6987secondary text, labels
--fg-faint#6C7597#98A0B8faint text, tertiary
--rulergba(242,240,234,.17)rgba(32,40,69,.14)hairlines, outlines, dividers
--rule-strongrgba(242,240,234,.32)rgba(32,40,69,.30)emphasised borders
--tile#F2F0EA#202845resting letter tile face
--tile-ink#202845#F2F0EAletter on a resting tile
--die-edge#7E828F#9496A0tile edge detail
--select#202845#F2F0EAselected (lit) tile fill
--select-ink#F2F0EA#202845letter on a selected tile
--select-strokergba(242,240,234,.5)rgba(32,40,69,.5)selected tile border
--lit#3D445C#D5D4D3lit surface (non-tile)
--lit-ink#F2F0EA#202845ink on a lit surface
--lit-edge#2D354F#E5E3E0lit edge detail
--trace#F2F0EA#202845the connecting trace line
--earned#7BE0A3#276A43positive — valid word / a win
--danger#F2B8BC#8E2430negative — invalid / error
--focus#7E9BE0#2F58D0focus ring
--earned-washrgba(123,224,163,.12)rgba(39,106,67,.10)green tint background
--danger-washrgba(242,184,188,.14)rgba(142,36,48,.10)rose tint background
--earned-card-bgrgba(123,224,163,.06)#E6EEE3earned card fill
--earned-card-borderrgba(123,224,163,.45)#9FC2A9earned card border
--mark-dot#9698A0#686D7Dbrand-mark lattice dots
--shadowrgba(10,14,34,.38)rgba(32,40,69,.16)drop shadow
--shadow-liftrgba(10,14,34,.28)rgba(32,40,69,.20)button lift shadow
--scrimrgba(10,14,34,.72)rgba(32,40,69,.55)modal scrim

Generated from css/tokens.css and the spec's colour table at 3.25.7.

Contrast

TokenDark · groundDark · cardLight · groundLight · card
--fg
body text
12.70AAA10.14AAA12.70AAA13.75AAA
--fg-soft
secondary text
6.36AA5.07AA4.78AA5.17AA
--fg-faint
tertiary
3.19UI only2.55decorative2.29decorative2.48decorative
--earned
earned (green)
8.98AAA7.17AAA5.71AA6.18AA
--danger
refused (rose)
8.52AAA6.80AA7.54AAA8.16AAA
--focus
focus ring
5.27AA4.21UI only5.38AA5.82AA

WCAG contrast, computed from the tokens on every build. AA needs 4.5:1 for text, 3:1 for large text and UI.

Body text, secondary text and both semantic hues pass AA on the ground and on cards in both modes. --fg-faint doesn't, which is why it's for non-text only (see Known issues).

The two semantic hues

Green (--earned) and rose (--danger) are the only colours that carry meaning, and each means exactly one thing.

  • Green means earned. A valid word, a win, a game you've finished today. It isn't for recaps, badges or progress, which are carried by weight and ink.
  • Green is never a selection (3.21.1). A word shown or traced on a review board lights in the pressed-tile state (--select, --select-stroke, --select-ink), and its pill flips the same way. Showing a word earns nothing.
  • Rose means refused. Not a word, a form error, the clock running out.
Dark
Light
One hue, one meaning the live word pill: neutral, earned, refused

Legacy names

Older class names still work because aliases point at the canonical roles in one place: --tray → --ground, --tray-raise → --raise, --tray-line → --rule, --paper → --fg, --paper-dim → --fg-soft, --gold → --fg, --ok-green → --earned, --bad-fill → --danger, and a dozen more. They're deprecated: never use them in anything new. Moving the existing uses over is planned (see System health).

Why it's built this way

Known issues and gaps

From the 3.23.6 audit:

  • --fg-faint reads 3.19:1 on dark and 2.29:1 on light, below the 4.5:1 text floor, yet three rules use it for text. Fix: those become --fg-soft.
  • 21 colour literals remain in component CSS, including a modal overlay that doesn't change in light mode and a pale blue (rgba(184,204,239,…)) in 8 rules that isn't in the palette.
  • 47% of colour references still use legacy names.
  • 6 focus rules use --gold and 2 use --focus. They should all use --focus.