Bowl quinoa & légumes rôtis
4 personnes · 25 min
520 kcal · 600 g
It fixes the decisions that are no longer up for debate: colour, type, space, grid, shape, motion, accessibility. Every value exists in dark and light — flip the theme and everything you see re-renders from the real tokens.
Dark by default. The near-black ground lets the lime carry the action; light is a mirror, not a second system.
Food first. The UI stays neutral so the dish photos are the only uncontrolled colour.
One accent per view. Lime = action, mint = progress, coral = alert. Never two roles for one colour.
The original system is mobile. The web adds a 4/8/12-column grid with a 1240px container, two extra type steps for large screens, hover and keyboard focus states, and an elevation scale.
Nothing is removed from the existing mobile system.
Step 500 is the flat brand colour, identical in both themes. The dark steps carry text on light grounds (the *-ink tokens); the light steps are used as grounds.
500 is the flat brand colour in both themes (CTA, active tab, progress ring). 900 is --lime-ink: lime text and borders in light mode (6.9:1).
500 is progress, links and the focus ring. 800 is --mint-ink (6.1:1). 100–200 are positive alert grounds.
500 is alerts, badges and notifications. 800 is --coral-ink (6.9:1). Never used for body text.
--gray takes 400 in dark and 500 in light: at 500 it is 3.3:1 on --bg-raised-2, below the 4.5:1 WCAG asks of the labels it carries. Steps 700–1000 are dark-mode surfaces, 100–200 light-mode ones.
A component never hardcodes a hex — it uses the token. That is what lets the light theme exist without duplicating a single component.
| Token | Dark | Light | Usage |
|---|---|---|---|
| --bg | #121212 | #F7F5EF | Page ground |
| --bg-raised | #191919 | #FFFFFF | Cards, panels, nav bar |
| --bg-raised-2 | #212121 | #ECE9DF | Fields, inactive chips, tracks |
| --text | #F5F5F0 | #15150F | Primary text |
| --text-dim | #B9B9B4 | #5B5A50 | Descriptions, metadata |
| --gray | #9CA3AF | #64635A | Labels, inactive icons, borders |
| --line | rgba(…,.08) | rgba(…,.12) | Separators and card outlines |
| --lime | #C4F135 | #C4F135 | Primary action (flat) |
| --lime-ink | #C4F135 | #4B5E12 | Lime as text or border |
| --mint | #2DD4BF | #2DD4BF | Progress, focus (flat) |
| --mint-ink | #2DD4BF | #0E6B5F | Mint as text or link |
| --coral | #FF6B4A | #FF6B4A | Alert, badge (flat) |
| --coral-ink | #FF6B4A | #9C321B | Coral as text or border |
Poppins ExtraBold for the brand voice and headings, Manrope for everything else, JetBrains Mono for quantities and units — tabular figures stop the text jumping when a value changes.
Manrope in a heading with Poppins in the body: the hierarchy flattens. Titles in Poppins, descriptions in Manrope, quantities in mono.
No value off the scale. If a spacing 'doesn't quite land', it's the composition to revisit, not the scale.
A card never mixes two surface radii.
In dark, elevation reads through the surface; in light, through the shadow.
Nothing lasts longer than 240ms: a cooking app gets read with dirty hands.
Six variants, one role each. Only one primary per view. Hover, focus, pressed, loading and disabled are shown for every variant.
Seven states. The focus ring is mint and appears instantly; error and success carry both a border and a hint, never colour alone.
Empty state uses the placeholder in gray
Adresse incomplète
Foyer rejoint
Géré par l'admin du foyer
Recipe cards lift 3px and zoom the photo 1.04 on hover. Selected carries an accent border and a check; unavailable drops to 55% and stops responding.
4 personnes · 25 min
520 kcal · 600 g
2 personnes · 30 min
640 kcal · 450 g
6 personnes · 40 min
480 kcal · 900 g
1 personne · 10 min
510 kcal · 320 g
The generic card takes the same hover elevation when it is clickable. It uses the medium radius; panels use the large one.
A native details element: no script, keyboard reachable, state announced.
A door per closed domain, counting what is on; the open one lays its options out under the row rather than over the page.
Working on — the wait while a model writes dishes
Plat 6 sur 14 · Dahl de lentilles
Same grid and stroke as the icons; drawn for the wait above and nothing else.
Panels carry more padding and the 20px radius. They group cards and controls rather than content of their own.
One 24-unit grid, one 1.7 stroke, currentColor. They were typographic characters before this — `⧉` for duplicate, which nobody reads as duplicate, and no glyph at all for edit, which is why that action had no button. Every icon is aria-hidden: the control around it carries the name.
A line in a list, written once — the shopping list, the journal, the household and the library all drew their own before this. The line is the surface, not the rule between two of them: a row separated by a hairline has no visible target to aim at. Below 480px the row wraps and the action rail drops to its own line.
Curry de lentilles corail
4 personnes · 40 min · 480 kcal · 22 P · 58 G · 14 L
Soupe de courge rôtie
6 personnes · 45 min · 414 kcal · 9 P · 52 G · 18 L
Lentilles corail
Épicerie · 2 recettes
One among few, chosen once. There were four of these — the pricing cycle, the three ways into the editor, the onboarding answers, the library's all/favourites — and each drew "chosen" differently. A track with a filled cursor, because a 14% tint reads as "on" only on a screen you are looking straight at. It is not a tab list and does not claim to be: role=group with aria-pressed says what it is.
Vertical for options too long to sit side by side. Still one among few: what changes is the room, not the question.
Fewer, more. Four copies existed — cooking mode, the editor, the meal sheet, the household size — differing in the button variant, the size of the figure, and whether the bounds were enforced on the click or only on the label. In one, the minus was an en dash and in another a minus sign, which is the kind of difference that survives for years because nobody can see it. The bounds live here now.
A block's title, and the action that belongs to it. Title left, action right, everywhere — guidance in Bastien and Scapin's sense: what a block can do belongs beside the block, so nothing has to be looked for. On a narrow screen it wraps rather than shrinking the title.
The block this titles goes here.
Sur les sept derniers jours, hors jours non notés.
A button that shows what else can be done, so a row of controls stays short. Seven actions in a recipe rail is 308px of buttons on a 360px screen, and the two that matter — cook it, plan it — would have been the ones to wrap. The trigger sits in the same place on every row and an item that does not apply is disabled inside the menu rather than missing from it. An item that goes somewhere renders as a link, so it can be opened in a new tab and a download is the browser's navigation rather than a script assigning `location`.
Previous, where you are, next. No numbered pages: a library is narrowed with its filters, not by jumping to page seven of twelve, and a row of numbers is a row of 44px targets a 360px screen cannot hold. The position is a sentence rather than “3 / 12”, and it is announced when it changes — pressing “next” moves rows somebody cannot see.
Three tones, because the system gives one meaning per colour. Errors take role=alert and interrupt; the rest wait their turn. `position="fixed"` pins one to the bottom of the viewport for something wrong with the page as a whole.
Cette page ne répond pas
One modal for the whole app. The overlay scrolls and the card is capped at the visible height, so the bottom of a long sheet is reachable on a 360×480 screen; the head stays put while the body scrolls. Escape closes it and focus opens on the close button.
Ajoute des recettes au planning et les ingrédients arriveront ici automatiquement.
La connexion a été interrompue. Tes recettes enregistrées restent disponibles hors ligne.
Cette page ne répond pas