Technical guide

Design

The UO design system

What makes every game here feel like an Unnecessary Obstacles game, and how each one keeps a look of its own inside one design system.

Checked against the code on 2026-10-30.

On this page
  1. The feel
  2. Three layers
  3. What a game may set
  4. How a rule is held
  5. Changing the system

Every game here looks different. The Register is a walnut desk, the card games are green felt, and the pencil puzzles are a puzzle book. They should still feel like one place: once you’ve learned how a sheet closes, where Undo is or what “New puzzle” does, it works the same in the next game. The design system is how that happens. It’s a short list of principles, the tokens and kits that put them into code, and the checks that hold every game to them.

These chapters are the system’s one source of truth (ADR 0055). A rule is stated once, in Principles, under a name; Rules and checks says what holds each one.

The feel#

David’s words for a UO game are human-first, ultra-readable, calm, delightful.

What it means here
Human-first It works the way you expect, for whoever’s playing, on whatever they hold. You’re never stuck, you never lose your place, and nothing is lost on one press.
Ultra-readable Every word is big enough, dark enough and has room. What you hunt for is the easiest thing on the screen to find.
Calm One loud thing at a time. Nothing moves under your finger. Things look different only when they mean something different.
Delightful Motion that explains, a flourish when something is yours to do, a celebration when you win, and each game’s own look.

Every principle serves one of the four, and the principles are grouped under them. Delight never costs the other three: a flourish never blocks a press, hides a word or is the only way something is said.

Three layers#

The system has three layers. Each builds only on the ones above it, and personality lives only in the last.

Layer What it holds In code
The site The principles, the tokens (the spacing, type and layout scale, the motion vocabulary), and the pieces every game shares: sheets, the result sheet, toasts, Options, keys, the way up The house kit, games/static/games/house/, and the paper (games/static/games/house/paper.css) the shelf, this guide and the puzzles are printed on
The family What games of one kind share The card room (games/static/games/cards/) on the room kit (games/static/games/room/) for card games; the room kit alone for board and party games; the pencil-puzzle kit (games/static/games/puzzles/) for puzzles, and its toy box look (games/static/games/brainteasers/) for brainteasers
The game Its personality: palette, faces, board, celebration, the voice of its headings The game’s own files, games/static/games/<slug>/

How the kits are built and plugged in is Front-end kits.

What a game may set#

A game re-skins the layers above it but never resizes or moves them. Its stylesheet sets its faces, every color (mapped onto the color jobs), its display weight, its button and panel radii, its rail, its edges and textures, and the layout of its own table between the bar and the dock. Everything else is the house’s: type sizes, the spacing scale, the gutter, the bar, button and tool sizes, sheets’ padding and motion’s durations.

npm run check:tokens holds that line. It fails a stylesheet that sets one of the house’s scale tokens to another size:

tools/tokens/check.mjsSCALE · lines 246–252
// ── the house's layout and type scale (docs/guide/design/tokens.md) ──
// The type roles and spaces every root declares have one size each (the
// rules type-roles and one-skeleton). The layout tokens are house.css's alone: every game on the
// kit inherits them (its page loads house.css), and no stylesheet anywhere
// sets one to another value, except the few that are a game's own.
export const SCALE = { '--t-heading': '20px', '--t-body': '16px', '--t-label': '16px', '--t-note': '14px',
  '--s1': '4px', '--s2': '8px', '--s3': '16px', '--s4': '24px', '--s5': '40px' };

It also fails any stylesheet but house.css that sets a layout token. The one exception is a game’s own corners:

tools/tokens/check.mjsGAMES_OWN · line 253
export const GAMES_OWN = ['--panel-radius'];

How a rule is held#

Every rule is held in at least one of three ways, strongest first.

  1. The kit is the only implementation. There is one sheet, one result sheet and one Options sheet, and every game calls them. A game can’t put Done in the wrong place, because it doesn’t draw Done. npm run check:house and npm run check:room test the kits.
  2. A check measures it. npm run check:tokens reads every stylesheet without a browser. npm run check:layout and npm run check:details open every game’s screens in a real browser and measure them (Testing and checks). Every finding ends with the name of the rule it breaks, and a test fails any rule that nothing holds (Rules and checks).
  3. By eye. What can’t be measured, such as whether the eye lands on the right thing first, is judged in the design audit before a release (the /design-audit skill, .claude/skills/design-audit/SKILL.md).

A rule held none of these ways is only a wish. Every rule in Rules and checks says how it’s held.

Changing the system#

  • A new rule goes in Principles, under the feel it serves, with a name of its own, and in Rules and checks with what holds it. David makes the design calls, so a new rule or a new look waits for his say-so.
  • A difference that’s meant goes in its check’s list of exceptions, with why: ALLOW in tools/house/details.mjs, EXCEPTIONS in tools/house/layout.mjs. Every other finding is fixed.
  • A new game wears its family’s kit, declares the tokens its kit reads (npm run check:tokens says what’s missing) and passes the checks.

Where the rest lives. These chapters replaced docs/design-language.md and docs/design-system.md, which now only say where each of their sections went (old rule numbers included). A game’s detailed design notes are in its own docs/<slug>.md; the room kit’s and the card room’s in docs/table-games.md; the shelf’s in docs/hub.md. Dated audits are in docs/audits/.