Design contract

For designers working on Localintel — in-house or contract. It is short on purpose; you should be able to read it in fifteen minutes and go back to designing.

What this is, and what it is not

This constrains the vocabulary, not the design. Which greys exist, which type sizes exist, what a breakpoint is allowed to change — that sort of thing. Nothing here tells you what to make, and none of it is a review checklist you have to satisfy before anyone will look at your work.

The reason it exists: on our last redesign, the parts that cost the most were never the interesting parts. They were a 13.5px font that had to become a hand-written rule, a card track drawn against a wider canvas than ours that silently lost a column, and a mobile layout that regrouped controls in a way that forced the same buttons to be built twice. All of it avoidable, none of it visible in the design file. This is that list, written down.

Everything below is live and inspectable in the boutique — one URL, the real components, the real tokens. This document is published there too, so if you are reading it on the web the links below already work; if you are reading the file, ask for the boutique link. Where it says /tokens, that is a page you can open, read values off, and inspect in DevTools.

1. The deliverable

Keep sending HTML prototypes. They are worth more to us than any static mockup, because we can read exact values out of them and see hover and open states without asking. Please carry on.

Two things make them much cheaper to implement:

  • Annotate values with token names rather than raw numbers where you know them — --color-heading-2 instead of #0D1420, text-heading-3 instead of 17.5px. See §2.
  • Say which parts are real and which are placeholder. Charts, maps, tables and KPI blocks in your file are almost certainly standing in for our live components, which bring their own layout. A one-line note per region — "real component, art indicative" versus "design this" — removes a whole class of guessing. Last time we had to ask.

2. Design on our scales

A value that is not on our scale is not slightly more expensive — it is a different kind of thing. On the scale, it is one word. Off it, it is a bespoke CSS rule that then has no dark mode and no responsive behaviour of its own, and that nobody else can reuse.

Concretely, from real cases: 17.5px cost nothing (it is a step, text-heading-3), and 13.5px cost a hand-written rule for a difference nobody could see next to 14px. Same for radii — 10px and 22px are steps; 19px is not, and on a short control what you almost certainly mean is a full pill, which is a step.

See /tokens for every value, with light and dark shown together.

  • Type — 23 steps. Size, weight and line height travel together under one name, so ask for a step rather than three numbers.
  • Radius — Tailwind's scale plus two named tokens (panel 10px, sheet 22px).
  • Spacing — a 4px scale. Any multiple of four is free; anything between two steps is not.
  • Colour — see below.

Colour has two halves, and they are used differently

This one is not obvious and it has bitten us:

  • Theme tokens — surfaces, primary, error, success, the control colours. Around fifty, and they generate real utilities.
  • :root variables — a larger set that includes every text-role colour: heading-1heading-5, subtitle, action, filter-option, date. These have no utility behind them and are referenced differently in code.

Practically, for you: name the role, not the value. --color-heading-2 for a page subtitle, --color-heading-5 for a caption. Pick by role, not by how dark it looks — heading-1 is the page title and heading-5 is a caption, and the darkness relationship between them inverts in dark mode.

Never specify a hex. A token carries a light and a dark value and survives a per-client re-theme. A hex gets you neither, and forces us to guess which of three near-identical greys you meant.

3. Both modes, always

The app runs in light and dark. Every colour needs both, and dark is not an inversion — contrast and accent legibility have to be decided, not derived.

If designing both doubles your work, the alternative that costs you nothing: name tokens instead of values (§2) and dark comes free, because the token already carries it.

Our last reference was light-only, which meant every colour was decided twice — once by you and once by whoever implemented it. Every specimen in the boutique is shown in both modes side by side for exactly this reason.

4. Breakpoints: one rule

There is one breakpoint that matters: md, at 768px. Below it is "mobile", above it is "desktop".

Across breakpoints, keep the same elements in the same containers. Change size, direction, order and visibility as freely as you like. Do not re-parent an element, and do not split one group into two.

Size, direction, order and visibility are nearly free — a row becomes a column, a control grows, something hides, the order shuffles. All four are a line of CSS.

Re-parenting is not free. If a control moves from one container to another between breakpoints, the platform cannot move it — so it gets built twice, once in each place, with one copy hidden. Two copies drift. That is a real cost you can spend deliberately, but you should know you are spending it.

Two examples from our own work, both on /patterns with the numbers:

  • Cheap. The index toolbar. One row at desktop; below md the row becomes a column, search goes full width, the filters become a scrolling strip, the view switch hides. Four changes, nothing moved containers, an afternoon.
  • Expensive. The preview modal. Desktop has two independently scrolling columns; mobile is one scroll with a pinned header and action bar, and the desktop side rail's five actions become a three-button bar plus an overflow menu. Both were rebuilds rather than restyles. Worth it here — but a design that kept one scroll region at both sizes would have cost nothing.

Also:

  • Design mobile at 375px, and sanity-check 320px. Our last mobile file was drawn wider than a real phone, so chips that fitted the canvas overflowed the device.
  • When a row is too wide, say what happens: scroll, wrap, or truncate. All three are buildable. Guessing between them is not.

5. Widths and grids

State the canvas width you drew against. Our content column caps at 1240px, with a page gutter of 36px (18px below md).

Specify a grid as a rule, not a track. "Four across at desktop, one on a phone, 22px and 30px gutters" is something we can satisfy at any width. A fixed 290px column only works on the canvas it was drawn on: at 1340px it gives four across, and in our 1240px container it quietly gives three. That happened, and the fix was arithmetic nobody could do from the file alone.

6. States, not just the happy path

The single most common gap. A screen in one state is one sixth of a screen.

For every control: default, hover, active, focus, disabled, loading. For every list or collection: loading, empty, error, one item, many items. For every field that can be missing: what shows instead — we use an em dash.

Why this one matters more than it sounds: a menu row in our app was correctly disabled in code for weeks but looked identical to a live row — same ink, same hover — because no design had ever shown a disabled menu row, so nobody had styled one. It read as broken rather than as not-yet-available.

7. Real content, including the worst case

Design with the longest plausible string in every field, and say what should happen when it does not fit.

A live example: an "Owner" row was designed showing You. The backend actually returns an email address, and a long one pushed the whole panel into a horizontal scrollbar. Both are one-line fixes if the question is asked — wrap, truncate, or scroll — and a guess otherwise.

Please also don't hand us dummy imagery to reproduce. Where a region shows real content, we will use the real content; a placeholder state ("No preview image") is worth designing, and we will use that.

8. What we add after you

Not gaps in your work — deliberately ours, so you don't have to carry them:

  • Accessibility. Focus rings, ARIA roles, keyboard navigation, contrast checks. A design that omits them is silent on the subject, not arguing against them, and we never remove existing accessibility to match a picture.
  • Two consequences worth leaving room for, because they are layout, not decoration: a focus ring needs about 2px of clearance around a control, so avoid placing focusable controls flush against a clipping edge; and mobile hit targets grow toward 44px, so a 34px control on a phone will probably get taller.
  • Safe areas. Where your mobile frame reserves space at the top and bottom for a notch or a home indicator, we measure the device rather than hardcoding your numbers. Design the padding; we will translate it.

9. Build from the inventory

Browse the components at /buttons, /forms and /overlays — with their variants, states, and limits. The exact inventory and count are in /design-system.json, which is generated from the code and cannot go stale.

  • Composing existing components: free.
  • Extending one with a new variant: usually minutes. A checkbox gained an indeterminate state and a stronger outline in about that.
  • A genuinely new component: real work, and worth flagging as such in your handover so it can be planned rather than discovered.

One current limit worth knowing, since it shapes mobile menus: every dropdown in the app is an anchored panel that hangs off the control that opened it. There is no bottom-sheet presentation yet. A mobile design built on sheets is asking for new work — which may well be the right call, but say so rather than assuming it exists.

10. When we deviate

Sometimes we will not match your design exactly, on purpose: when the difference is imperceptible and the exact version would cost a bespoke token, a new component, or a fragile override. Our own rule is match exactly where it is cheap, approximate where exact is expensive, and never silently.

So every deliberate deviation gets written down and sent back to you — what we changed, and why. You can accept it or push back. What we are trying to avoid is not deviation; it is drift nobody mentioned.

Pre-flight

Six questions. If they are all yes, the handover will go smoothly.

  1. Are colours named as tokens rather than hexes, and does every colour have a dark value?
  2. Are type sizes, radii and spacing on our scales — or, where they are not, is that a deliberate ask?
  3. Does every breakpoint change amount to size, direction, order or visibility only? If something is re-parented or regrouped, is that called out?
  4. Is the mobile view drawn at 375px, and does every over-wide row say whether it scrolls, wraps, or truncates?
  5. Are grids expressed as N across at width W, and is your canvas width stated?
  6. Does every control show its disabled and loading states, and every list its empty and loading states?

And the two annotations that save the most time: which regions are real components, and what the longest realistic string is in each text field.