Parlour

The design system

One palette, one type ramp, one set of states and one icon, shared by all four interfaces.

Parlour has four interfaces: this site, the menu bar app, the phone page the CLI serves, and the iOS app. They use three languages and three toolchains, and none of them declares a colour of its own.

packages/design holds one file of tokens and a few emitters that write it into every surface. The generated files are checked in, so a checkout builds without running the emitter first. A test fails when any of them has drifted, so pnpm check catches a token change that was not emitted.

pnpm exec nx run design:emit      # after editing packages/design/src/tokens.ts
pnpm exec nx run design:icons     # after changing the mark, or a colour it uses
pnpm exec nx run design:test      # contrast, and whether anything is stale
Generated fileRead by
apps/site/app/tokens.cssThis site
apps/desktop/src/tokens.cssThe menu bar app, which maps it onto shadcn's token names
packages/parlour/src/server/web/tokens.cssThe phone page
apps/ios/Parlour/DesignSystem/Tokens.swiftThe iOS app
apps/ios/.../AccentColor.colorset/Contents.jsoniOS controls the app never styles, in hearth green
apps/ios/.../AppIcon.appiconset/Contents.jsonThe iOS app icon set
apps/site/app/icon.svg, packages/parlour/src/server/web/icon.svgThe favicon

Ink on a limewashed wall, with one lit thing

Paper, ink, bracken and a hairline rule carry the whole interface. Only two colours are allowed to mean something, and nothing else is colourful. That is what makes those two readable at a glance from across a room.

TokenWhat it means
paper, surfaceThe wall, and a panel lifted off it by a shade rather than a shadow
ink, brackenWhat is said, and what is said quietly
rule, insetA hairline, and anything recessed into the wall
hearthThe house is alive: running, listening, speaking, the primary action
lampThe one lit thing: the wake word, the page you are on, thinking, a warning
alarmSomething failed and a person needs to act

Amber on a pale wall cannot carry text. So lamp is only ever a dot or a halo, and lampText is the darker value used for words. Every token that carries text clears 4.5:1 against its paper in both schemes, and a test checks it.

The states are part of the design system

Every surface shows the same five states, so they live with the colours rather than in each app. Each state has a colour, a cadence and a word.

StateDrawn asOne breath
stoppedAn outline in brackenstill
idleA steady hearth mark, labelled "ready"still
listeningA lit hearth mark1100 ms
thinkingA lit lamp mark700 ms
speakingA lit hearth mark500 ms

The cadence quickens as the turn nears its answer, so you can tell what the house is doing without reading a word. The menu bar dot, the phone page's talk button and the iOS state mark breathe in step, because they read the same numbers from the same file. Under Reduce Motion the iOS mark stops breathing and the colour carries the state alone.

The icons

The house mark (a roof, the walls, a dado rail and one lit lamp in the gable) is drawn once in src/icon.ts from the palette, in four shapes: the favicon tile, a full bleed square for iOS and the phone page's home screen, the macOS tile on Apple's grid, and a bare template for the menu bar. design:icons rasterises them into every bitmap a platform wants: the site's favicon, the phone page's home screen icons, the iOS app icon, and the menu bar app's icon.png and tray.png. The PNGs are binary, so the drift test cannot check them. Regenerate them and look.

Changing something

Edit packages/design/src/tokens.ts, run the emitter, and commit the generated files with your change. A new surface gets a Target in src/emit.ts and a line in the list in emit.test.ts. If a surface needs a format none of the emitters produce, add another emitter next to css.ts and swift.ts instead of keeping a hand-maintained copy of the values.

On this page