Design lessons learned
What the Space Works build got wrong, how it was fixed, and the rule that came out of each fix. It is written for the people building the other Space Works apps (web, Android, iOS, sibling apps) so they can skip the mistakes we already paid for.
Most of the pain was colour in light and dark. A palette that looks right in one theme quietly fails in the other: teal that reads well on cream is nearly invisible on black, a pastel pill that is calm on cream glares on black, a hairline that frames a card on cream disappears on black. So most of this page is about colour. The rest covers type, motion, components and the process that keeps all of it from drifting.
How to read a lesson. Each one has a Problem (what went wrong), a Rule, the Values involved, how it is
Enforced (a test, guard or file) and a Source (commit hash in the app repo SpaceWorks or in this design
repo, or a decision number from the build’s decisions ledger). Where a rule is derived from the tokens rather
than from a shipped fix, the lesson says so.
Words used here.
- Token: a named colour (for example
panel) instead of a hex value typed into a screen. - Contrast ratio: the WCAG number from 1 to 21 for how far apart two colours are. Text needs 4.5 or more; icons, borders and focus rings need 3 or more.
- Ink: a colour meant for text and small marks (for example
accent-ink). A fill is a colour meant for a filled background, with its own label colour (on-accent). - Soft: a quiet, opaque tinted background (for example
success-soft).
All hex values come from tokens/tokens.json. Ratios were computed with the WCAG formula from those values.
The light and dark pairing table
Section titled “The light and dark pairing table”One row per role. Ratios are measured against the background named in the last column. “n/a” means the colour is not meant to be read against that background. Mobile apps should use these exact values (convert to the platform colour type) and not re-derive them.
| Role | Light | Dark | Light ratio | Dark ratio | Measured against |
|---|---|---|---|---|---|
Page background (bg) |
#FFF4E8 |
#000000 |
n/a | n/a | the page itself |
| Surface | #F6ECE0 |
#0B0B0B |
1.08 | 1.07 | bg (a layer step, not text) |
| Panel (cards) | #FFFFFF |
#141414 |
1.08 | 1.14 | bg |
| Hover | #EFE6DB |
#1C1C1C |
1.14 | 1.23 | bg |
| Line (hairline, decorative) | #D5D0C6 |
#2C2C2C |
1.42 | 1.50 | bg (1.54 / 1.32 on panel) |
| Outline (control edge) | #828A83 |
#787878 |
3.27 | 4.76 | bg (3.55 / 4.17 on panel) |
Text (ink) |
#142C2A |
#FFF4E8 |
13.60 | 19.36 | bg (14.75 / 16.98 on panel) |
| Muted text | #5B6863 |
#ACACAC |
5.37 | 9.25 | bg (5.83 / 8.12 on panel) |
Accent fill (accent) |
#006E63 |
#006E63 |
5.68 | 3.41 | bg (6.16 / 2.99 on panel); never carry meaning alone in dark |
Label on accent (on-accent) |
#FFF4E8 |
#FFF4E8 |
5.68 | 5.68 | the accent fill |
Accent text and small marks (accent-ink) |
#006E63 |
#80B1A6 |
6.16 | 7.68 | panel (5.68 / 8.76 on bg) |
| Focus ring | #006E63 |
#DFFF8F (Citron) |
5.68 | 18.84 | bg (6.16 / 16.53 on panel) |
| Success fill / label | #DFFF8F / #142C2A |
same | 13.23 | 13.23 | the fill |
| Success text on soft | ink #536D49, soft #FBFFF2 |
ink #E9FCAA, soft #20221B |
5.67 | 14.52 | its own soft |
| Warn fill / label | #FFD447 / #142C2A |
same | 10.37 | 10.37 | the fill |
| Warn text on soft | ink #696834, soft #FFFAE9 |
ink #FFDE77, soft #222017 |
5.54 | 12.41 | its own soft |
| Info fill / label | #B5CDFF / #142C2A |
same | 9.23 | 9.23 | the fill |
| Info text on soft | ink #51697B, soft #F6F9FF |
ink #CBD9F8, soft #1E1F22 |
5.45 | 11.63 | its own soft |
| Danger (text and fill) | #B91C1C |
#FCA5A5 |
6.47 | 9.71 | panel (5.96 / 11.06 on bg) |
| Label on danger fill | #FFF4E8 |
#142C2A |
5.96 | 7.77 | the danger fill |
Danger card border (danger-line) |
#FCA5A5 |
#7C6261 |
1.90 | 3.31 | panel (decorative) |
| Emphasis fill (Workshop Orange) | #FF7447 |
#FF7447 |
2.47 | 7.84 | bg; as a fill only, label #142C2A is 5.51 in both |
| Emphasis soft | #FFEEE9 |
#221A17 |
13.11 | 15.76 | text on it (ink) |
| Sidebar | #DFFF8F (Citron) |
#0B0B0B |
13.23 | 18.14 | ink on the sidebar |
| Sidebar active item | #006E63 |
#006E63 |
5.68 | 5.68 | on-accent label on it |
| Scrim (dialog backdrop) | #142C2A |
#000000 |
n/a | n/a | see lesson 17 |
| Chart edge (SW-380) | #142C2A |
#FFF4E8 |
14.75 | 16.98 | panel (12.64 / 18.14 on surface, 13.60 / 19.36 on bg); the mark boundary |
| Chart fills 1-5 (SW-380) | same bright brand colours | same | 1.11 to 6.16 | 2.99 to 16.53 | panel; fills alone may miss 3:1, the edge carries it |
| Chart: danger | #B91C1C |
#FCA5A5 |
6.47 | 9.71 | panel; not a series colour |
| Loader: box under-face | #354845 |
#66625D |
9.70 | 3.04 | panel (8.94 / 3.47 on bg) |
| Loader: spark | #142C2A |
#FFF4E8 |
14.75 | 16.98 | panel |
| Loader: seam | #142C2A |
#000000 |
14.75 | n/a | panel; in dark the seam is the page colour on purpose (lesson 24) |
Things this table shows at a glance:
- Dark outline (4.76) is easier to see than light outline (3.27); dark line is barely there (1.5). That is deliberate (lessons 3 and 4).
- Teal as a fill is 3.41 on black and 2.99 on the dark panel. That is below the 4.5 needed for text, which is why
dark text and icons use
accent-ink(lesson 5). - Orange as text on cream is 2.47, so orange is a fill with a dark label, never text (lesson 12).
- Every status fill is the same hex in both themes; only the text-on-tint colours change (lessons 9 and 10).
On this topic
Section titled “On this topic”- 1. Colour in light and dark Surfaces, borders, text, accent, focus and status colours.
- Colour: charts, tenant colours and artwork Chart series, tenant brand colours, logos and illustrations.
- Typography, motion and components The lessons outside colour.
- Process, other apps and the five costliest lessons The process that stops drift, notes for Android, iOS and sibling apps, and the five lessons that cost the most.