The single source of truth. New button system with no trace of the Duolingo slab, three-layer tokens, and every interactive state specified rather than implied. This supersedes the lantern, daylight and hybrid pages.
The old one was amber, with dark bold text, on a rounded rectangle with a coloured bottom edge. That combination is the Duolingo signature. Shrinking the edge from 4px to 2px did not change what it was, it just made it quieter.
Two changes fix it properly. The silhouette becomes a pill, which reads nothing like a game key. And the primary goes dark with amber demoted to an arrow chip, so the lantern colour becomes an accent doing a job rather than the whole button.
Primitive values, then semantic aliases, then component overrides. This is the structure the current codebase is missing, and it is why five competing flat token sets ended up in there.
Raw values. These change almost never.
What each value is for. This layer is what a theme swaps.
| Semantic token | Points at | Purpose |
|---|---|---|
--surface-page | --cream-50 | The page. Always light |
--surface-card | --white | Routine cards |
--surface-focal | --navy-800 | The one dark card, the next action |
--surface-band | --cream-100 | Tinted section band |
--text-primary | --ink-900 | Body and headings on light |
--text-secondary | --ink-500 | Supporting text on light |
--text-on-focal | --cream-50 | Text inside the dark card |
--action-primary | --navy-800 | Primary button fill |
--action-accent | --amber-400 | The lantern. Chips, glows, on-dark fills |
--action-link | --amber-800 | Amber as text, the only legible amber on cream |
--border-subtle | --cream-200 | Hairlines |
--focus-ring | --amber-800 | Focus ring on light. 5.8:1, clears the 3:1 bar for UI |
--focus-ring-dark | --amber-400 | Focus ring inside the dark card |
| Token | Value | Note |
|---|---|---|
--btn-radius | 999px | Pill. The single biggest break from the old slab |
--btn-pad | 15px 32px | Primary and secondary |
--btn-chip | 36px | The arrow chip diameter |
--card-radius | 20px | White cards |
--focal-radius | 22px | The dark card |
--ring-width | 2px | Focus ring |
--ring-offset | 2px | Gap between element and ring |
--tap-min | 44px | Any target. 56px on a child surface |
--tap-gap | 8px | Minimum gap between adjacent targets |
Text needs 4.5:1. Non-text UI such as a focus ring needs 3:1. Both thresholds are applied below.
| Pairing | Ratio | WCAG AA |
|---|---|---|
| Body on page | 14.7:1 | Pass (needs 4.5:1) |
| Secondary on page | 5.6:1 | Pass (needs 4.5:1) |
| Text in the dark card | 15.7:1 | Pass (needs 4.5:1) |
| Secondary in dark card | 8.0:1 | Pass (needs 4.5:1) |
| Label on primary button | 15.7:1 | Pass (needs 4.5:1) |
| Arrow on the amber chip | 9.4:1 | Pass (needs 4.5:1) |
| Link text on page | 5.8:1 | Pass (needs 4.5:1) |
| Focus ring on page | 5.8:1 | Pass (needs 3:1, UI component) |
| Focus ring in dark card | 9.4:1 | Pass (needs 3:1, UI component) |
The child home with the new buttons. Icons are SVG, never emoji.
Today
About 25 minutes. You'll need a jar, warm water and a plate.
Two lessons left
Done this week
One to catch up
--amber-800, because --amber-400 fails contrast on cream.