Built so a second
designer could
pick it up
Eighteen screens only stay consistent if the values behind them are named. Every colour, space and radius resolves through a variable.
52Design tokens across colour, spacing, radius and border — one source of truth for every value.
24Components, each documented in every state, from hover and pressed to focus, disabled and selected.
14Documentation sections, including accessibility, motion, patterns and a full token table.
AAEvery text pairing measured against WCAG 2.1, with transparency composited over the surface behind it.
Colour
Three brand colours, one of them for action
Secondary text and borders are not separate greys. They are the brand green at a set opacity, which is why the interface stays warm instead of drifting grey.
brand/terracotta#c0704aThe one accent. It marks what the customer can do, which is to buy, add and save.
brand/dark-green#0b2b16Text, ink surfaces and the dark button.
brand/olive#49602fSuccess states and small marks.
Surfaces
Four surfaces, two for photographs
Eleven near-white backgrounds had accumulated and none were distinguishable. Four surfaces now cover every case; two more sit behind product photography.
surface/page#ffffff
surface/sand#f7f5f1
surface/blush#fff9f7
surface/ink#0b2b16
surface/image-cool#e5ebe6
surface/image-warm#ded8d0
Ink ramp
Six opacities do the whole job
Contrast ratios are measured on white, with transparency composited over the surface behind. The step used for small secondary text is the lightest one that clears AA, which is not the one that looks right by eye.
ink/90Secondary text is ink, not grey11.36AAA
ink/70Secondary text is ink, not grey5.38AA
ink/55Secondary text is ink, not grey3.64Large text only
ink/40Secondary text is ink, not grey2.41Fails
ink/18Secondary text is ink, not greyBorders
ink/08Secondary text is ink, not greyFills
Type
Nunito to say it, Quicksand to run the interface
Two families with one job each. Nunito carries display and editorial weight; Quicksand runs labels, prices, navigation and buttons. Nothing is set outside this ramp.
Heading/XLNunito SemiBold · 48
Plants for your light
Label/ProductNameQuicksand Medium · 28
Monstera Deliciosa
Label/PriceQuicksand SemiBold · 20
€84.00
Label/ButtonQuicksand Bold · 20
Explore Plants
Nav/TextQuicksand Medium · 18
Indoor plants Pots & Planters Plant care
Body/RegularNunito Regular · 16
Bright indirect light, water when the top inch of soil is dry.
Label/CategoryQuicksand Medium · 14
Pots & Planters
Caption/BadgeQuicksand Bold · 10
Pet safe
Space
An 8 px base with the half-steps the product needed
The screens held 26 different gap values before the audit. Accidents were snapped, the ones doing real work were kept. Layout tokens sit on top, with a content margin of 200, a card gap of 32 and a section gap of 80.
Grid and layout
One grid, and the margin does the adapting
What changes between breakpoints is the margin and how many columns a card takes. Content never exceeds 1200, so on a wide monitor the margin grows instead of the text line. Sections are separated by 80, a header sits 32 above its content, and a sub-group 48 below the one before it.
Desktop1600 and up12 columns · 200 margin · 32 gutter
Product grid runs 4 up. The hero is full-bleed.
Tablet768 – 15998 columns · 48 margin · 24 gutter
Product grid drops to 2 up. Filters collapse into a sheet.
Mobile360 – 7674 columns · 20 margin · 16 gutter
Single column. The sticky Add to bag bar appears here.
Components
One primary button, and it is terracotta
Every button in every state, built from the real values in the file. Primary is terracotta and there is only one on a screen; dark is the quiet alternative; chips show a selection two ways, fill and weight, never colour alone.
Explore PlantsPrimary · default
Explore PlantsPrimary · hover
Explore PlantsPrimary · pressed
Explore PlantsPrimary · disabled
Explore PlantsDark · default
Explore PlantsDark · hover
Explore PlantsDark · pressed
Explore PlantsDark · disabled
Add to BagAdd to Bag · default
Add to Baghover
Load more plantsLoad more · default
Indirect lightPet friendlyFilter chip · default, active
Motion
Movement confirms that something happened
Nothing animates for its own sake, and nothing that moves may delay a tap. Under reduced motion every duration collapses to zero and transforms are dropped, but state still changes colour.
Instant0 msnonePressed states. A button must look pressed on the same frame as the finger lands.
Quick160 msease-outHover, focus ring, chip selection. Short enough that it never gates a second click.
Standard240 mscubic-bezier(.2,.8,.2,1)Card lift, dropdown open, filter drawer. The default for anything that changes shape.
Considered420 mscubic-bezier(.16,1,.3,1)Add to bag. The bag count changes and the item flies in. The one place a customer should notice.
What it forbids
Mistakes that were really in the file
Each of these was in the file before the audit, not a hypothetical. They are written down so they do not come back.
- Pick the surface from the rampEleven near-white backgrounds had accumulated and none were distinguishable. Four surfaces now cover every case.
- Secondary text is ink, not greyTailwind defaults had leaked in (#111827, #9CA3AF, #6B7280) and read cold next to a warm green page. Secondary text is brand green at an opacity.
- Check the ramp before adding a stepSixteen different opacities of the same green were in use. Six do the whole job. If a design needs a seventeenth, the problem is the design.
- One accent, reserved for actionTerracotta marks what the customer can do, which is to buy, add and save. The moment it decorates a heading it stops meaning anything.
- Measure contrast, do not eyeball itink/55 looks perfectly readable and scores 3.64, below AA for body text. 242 small text nodes were moved up to ink/70 after measuring.
- Style sheet follows the productColor/Accent documented #D49B89 while every screen used #C0704A. A token nobody checks is worse than no token.