Aller au contenu
Fuelr — Design System
Foundations for the responsive web app · v0.2
What this document does

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.

Three principles

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.

What changes for the web

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.

01

Colour ramps — 100 to 1000

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.

Lime--lime
100#FAFCE6
200#F2F8C4
300#E4F294
400#D2EB5D
500base
600#A6CB1B
700#85A414
800#65800F
900ink · light
1000#2E3A08

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).

Mint--mint
100#E3FBF7
200#C4F5EC
300#97ECDD
400#5FE0CB
500base
600#1DB3A0
700#158F80
800ink · light
900#0A4C43
1000#062E28

500 is progress, links and the focus ring. 800 is --mint-ink (6.1:1). 100–200 are positive alert grounds.

Coral--coral
100#FFEDE7
200#FFD3C4
300#FFAD8C
400#FF8A66
500base
600#E8532F
700#C43F22
800ink · light
900#742513
1000#4D1A0D

500 is alerts, badges and notifications. 800 is --coral-ink (6.9:1). Never used for body text.

Neutral--gray
100#F3F4F6
200#E5E7EB
300#D1D5DB
400base
500#6B7280
600#4B5563
700#374151
800#1F2937
900#111827
1000#030712

--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.

02

Semantic tokens

A component never hardcodes a hex — it uses the token. That is what lets the light theme exist without duplicating a single component.

TokenDarkLightUsage
--bg#121212#F7F5EFPage ground
--bg-raised#191919#FFFFFFCards, panels, nav bar
--bg-raised-2#212121#ECE9DFFields, inactive chips, tracks
--text#F5F5F0#15150FPrimary text
--text-dim#B9B9B4#5B5A50Descriptions, metadata
--gray#9CA3AF#64635ALabels, inactive icons, borders
--linergba(…,.08)rgba(…,.12)Separators and card outlines
--lime#C4F135#C4F135Primary action (flat)
--lime-ink#C4F135#4B5E12Lime as text or border
--mint#2DD4BF#2DD4BFProgress, focus (flat)
--mint-ink#2DD4BF#0E6B5FMint as text or link
--coral#FF6B4A#FF6B4AAlert, badge (flat)
--coral-ink#FF6B4A#9C321BCoral as text or border
03

Typography

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.

Display
44 / 40 · 800
Fuelr.
H1
32 / 28 · 800
Planning de la semaine
H2
22 / 20 · 800
Liste de courses
H3
16 · 700
Poulet basquaise
Body
15 · 500 · 1.5
Planifie tes repas, atteins tes objectifs.
Meta
13 · 600
4 personnes · 25 min
Label
11 · 700 · .02em
Petit-déjeuner
Data
13 · mono
420 kcal · 600 g
Rules
  • Max body line length — 68ch
  • Heading leading — 1.1 to 1.2
  • Smallest on-screen size — 11px (labels)
  • Poppins allowed from — 14px
  • Uppercase — labels only
Avoid

Manrope in a heading with Poppins in the body: the hierarchy flattens. Titles in Poppins, descriptions in Manrope, quantities in mono.

04

Spacing — 4px base

No value off the scale. If a spacing 'doesn't quite land', it's the composition to revisit, not the scale.

--sp-14pxicon micro-offset
--sp-28pxlabel ↔ field
--sp-312pxgap between chips
--sp-416pxcard padding (mobile)
--sp-520pxmobile screen margin
--sp-624pxgrid gutter
--sp-832pxpanel padding
--sp-1040pxdesktop screen margin
--sp-1248pxclickable row height
--sp-1664pxgap between sections
--sp-2080pxpage top margin
--sp-2496pxbottom safe area
05

Shape, elevation, motion

Radii
--r-sm 12px
fields, tags
--r-md 20px
cards, rows
--r-lg 24px
panels, dialogs
--r-full 999px
buttons, chips

A card never mixes two surface radii.

Elevation
e0
border only · in-flow cards
e1
card hover
e2
menus, toasts
e3
modals, sheets

In dark, elevation reads through the surface; in light, through the shadow.

Motion
  • Hover, colour, theme — 120–180ms
  • Check, chip, switch — 160ms
  • Sheet, modal — 240ms
  • cubic-bezier(.2,.8,.2,1)
  • Reduced motion — honoured, everything becomes 0ms

Nothing lasts longer than 240ms: a cooking app gets read with dirty hands.

06

Buttons

Six variants, one role each. Only one primary per view. Hover, focus, pressed, loading and disabled are shown for every variant.

primary
one action per view · 46px
secondary
1.5px gray border
tertiary
raised-2 ground, no border
text
no surface · underline on hover
danger
coral outline → filled on hover
soft
accent 14% ground · dense lists
dangerText
destructive, no surface · inside a row
quiet
neutral, no surface · a rail of icons
Icon buttons & full width
07

Fields

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

08

Selection controls

Checkbox
Radio
Switch
09

Chips, tabs & badges

Filter chips
Tabs
Badges
PopulaireNouveauPlanifiéPériméGratuit
10

Cards

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.

Végé

Bowl quinoa & légumes rôtis

4 personnes · 25 min

520 kcal · 600 g

Saumon grillé, riz citronné

2 personnes · 30 min

640 kcal · 450 g

Curry de lentilles corail

6 personnes · 40 min

480 kcal · 900 g

Wrap poulet & avocat

1 personne · 10 min

510 kcal · 320 g

Interactive card

The generic card takes the same hover elevation when it is clickable. It uses the medium radius; panels use the large one.

DisclosureA block that opens — for a form used now and then

A native details element: no script, keyboard reachable, state announced.

Filter group

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

Food icons

Same grid and stroke as the icons; drawn for the wait above and nothing else.

  • drumstick
  • steak
  • fish
  • egg
  • tomato
  • onion
  • carrot
  • potato
  • pepper
  • mushroom
  • lemon
  • apple
  • leaf
  • bowl
  • bread
  • cheese
  • pot

Panel

Panels carry more padding and the 20px radius. They group cards and controls rather than content of their own.

11

Icons

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.

arrowUp
arrowDown
pencil
copy
trash
close
star
check
plus
minus
search
chevronRight
chevronDown
book
calendar
cart
journal
people
menu
arrowLeft
arrowRight
play
pause
clock
alert
more
flame
calendarPlus
12

List rows

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

  • LC

    Lentilles corail

    Épicerie · 2 recettes

    400 g
13

Segmented control

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.

14

Stepper

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.

md · 44px
4 personnes
xl · 56px · cooking mode
4 personnes
15

Section head

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.

Énergie

Sur les sept derniers jours, hors jours non notés.

16

Menu

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`.

17

Pagination

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.

18

Banners

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.

Mot de passe modifié.
Confirme ton adresse pour que Fuelr puisse te joindre.
19

Dialogs

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.

20

Data & system states

Ta liste de courses est vide

Ajoute des recettes au planning et les ingrédients arriveront ici automatiquement.

Chargement impossible

La connexion a été interrompue. Tes recettes enregistrées restent disponibles hors ligne.