One Link

Light theme · web and mobile · v1

The One Link design system

One Link runs a grain and fertilizer business: trades and mill coverage, weighbridge intake, stock, and farmer input finance, with a portal for counterparties and a field app for drivers. Every screen leads with what needs attention, and every figure is derived, never typed.

  • White lifts, grey recedes

    Cards, tickets and figure buttons are white on white, held apart by very soft shadows. Grey marks only what is secondary or switched off.

  • Greys tinted navy, and see-through

    Text and hairlines are navy-black at set opacities, so one token reads right on white and on grey. Each has a solid twin for use over photos.

  • Two layers of colour

    Components use semantic tokens only. Those point at 11-step palettes, so a palette change never touches a component.

  • The commodity takes over

    Blue is for links and entry points. Inside a commodity, its colour drives the share bars, chart lines and the trade ticket. Decisions stay primary dark.

  • Physical feedback

    Anything that commits sits on a 4px edge and sinks into it. Buttons scale to 0.97, motion settles on a spring, and every working button shows it is working.

Hover the card for the commodity wash. Press a figure to sink it. Switch commodity in the rail.

Rules

These come from how the business works, not from taste. Every component and screen obeys them.

Exceptions are the interface

Screens answer "is anything wrong?" first. Nothing to do is a designed, confident state, not an empty one. Show changes, not levels.

Every figure is derived, and can show how

Balances, stock, exposure and margin are computed, never typed. A headline figure reveals the subtraction behind it in one interaction.

Provenance and confirmation never merge

How a weight was captured and whether the counterparty agrees are two separate chips, with two separate icons.

Absent is not zero

A figure withheld from a role is genuinely missing. It shows as a quaternary dash with eye-off, and never as K0, blank, loading or blurred.

Three outcomes, three looks

Within tolerance books. Held for review goes to the owner. A hard block, like tare above gross, never books and is shown calmly, not as an error flood.

Nothing is edited

Records are append-only. A correction is a compensating entry with a reason, an actor and a time, beside the original.

Freshness is visible, degraded modes are loud

A silent weighbridge feed makes the position look flat, so sync health is always shown, and silence is unmistakable. Offline is a state with a queue.

Every working button shows it is working

The label fades and scales to 0.8 while a spinner takes its place. The button keeps its size and stays busy until the action lands.

Status is always an icon and a word, never colour alone. Icons are Lucide only: no emoji, no Unicode symbols standing in for icons. Charts are TradingView Lightweight Charts.

Colour

Two layers. Components use only the semantic tokens; the "Points to" column shows the palette step each one resolves to. Hover mixes 5% black into light fills and 10% white into filled buttons.

Surfaces

White does the lifting. Grey is kept for what recedes: search, unselected capsules, disabled fills, closed records.

TokenValuePoints to
--surfacePage ground #ffffff gray-50-solid
--elevatedCards, figure buttons, quantity chips, tickets #ffffff gray-50-solid
--groupedSecondary tiles, skeletons, row hover #f4f5f7 gray-100-solid
--grouped-lightSearch, unselected capsules, disabled fills #00153f0b gray-100
--grouped-elevatedGrey tile inside a card #f4f5f7 gray-100-solid
--invertedInverted chips, chart crosshair labels #23272d gray-900-solid
--overlayDialog and sheet scrim #00000066 literal

Content

Text and icons are navy-black at set opacities, so one token reads right on white and on grey.

TokenValuePoints to
--content-primaryTitles, figures, main text Aa#00050cdc gray-900
--content-secondaryMeta lines, units, secondary text Aa#000c1c94 gray-700
--content-tertiaryInactive tabs, captions. Never a figure Aa#000f2174 gray-600
--content-quaternaryChevrons, placeholders, the restricted dash Aa#00102a40 gray-400
--content-disabledDisabled labels Aa#00152e54 gray-500
--content-accent-brandLinks, active stepper value Aa#2563EB brand-500
--content-accent-upPositive delta text Aa#007e26 green-600
--content-accent-downNegative delta text Aa#df0c10 red-500
--accent-up-fillUp triangle, in-bars, live dot. Fills only, never text Aa#04af52 green-400

Solid twins

The same greys as solid colours, for text laid over a photo, where translucency would pick up the image.

TokenValuePoints to
--content-primary-solidPrimary text over photos Aa#23272d gray-900-solid
--content-secondary-solidSecondary text over photos Aa#6b727b gray-700-solid
--content-tertiary-solidCaptions over photos Aa#8b929b gray-600-solid
--content-quaternary-solidChevrons over photos Aa#bfc3ca gray-400-solid

Status

Subtle is the pale fill, strong is the text on it. Status is always an icon and a word, never colour alone.

TokenValuePoints to
--info-subtleInfo banner fill #f7fbfe brand-50
--info-strongInfo banner text Aa#0135aa brand-700
--success-subtleClean, within spec, confirmed #e4ffe9 green-50
--success-strongText on success Aa#006118 green-700
--warning-subtleAttention, amber band, held for review #fefce8 yellow-50
--warning-strongText on warning. #a16207 rather than #ca8a04, for AA Aa#a16207 yellow-700
--error-subtleBreach, red band, hard block, sync silent #fff6f2 red-50
--error-strongText on error Aa#bc0000 red-600

Lines and actions

Hairlines, the button fills, and the BETA badge.

TokenValuePoints to
--borderCard outlines, dividers, selected capsule border #00123719 gray-200
--border-light3D edge, table row dividers #0000000f literal
--border-elevatedOutlines on elevated layers #00123719 gray-200
--buttons-primaryDecisions: Approve, Finalise, Resolve case #23272d gray-900-solid
--buttons-brandEntry points: New trade, Raise GRN #2563EB brand-500
--buttons-criticalDecline, Void batch #df0c10 red-500
--buttons-labelLabels on filled buttons #ffffff gray-50-solid
--hover-overlay-darkerSubtle button fill, keyboard hint #0000000d literal
--beta-fillBETA badge #2563EB brand-500
--beta-labelBETA badge label #ffffff gray-50-solid

Palettes

Eleven steps each. The brand ramp keeps the reference's lightness per step, with One Link blue at 500.

graySolid greys for use over images
50ffffff
100f4f5f7
200e6e8ec
300d4d7dc
400bfc3ca
500abb2bb
6008b929b
7006b727b
800454a52
90023272d
950000000
gray, translucentTranslucent navy greys, what the content tokens point at
50ffffff
10000153f0b
20000123719
3000014332b
40000102a40
50000152e54
600000f2174
700000c1c94
800000711ba
90000050cdc
950000000
brandOne Link blue, 500 is #2563EB
50f7fbfe
100cddffb
200a3c2f7
30079a4f4
4004e84f0
5002563EB
6000e4bd1
7000135aa
800002687
90000165c
950000734
greenUp, success
50e4ffe9
100cdf3d7
2008fe5a5
3003ad26d
40004af52
500009c39
600007e26
700006118
80000450a
900002a01
950001200
redDown, error
50fff6f2
100ffcfc6
200ffa597
300ff7464
400fe3d33
500df0c10
600bc0000
700930000
8006b0000
900460000
950240000
yellowWarning
50fefce8
100fef9c3
200fef08a
300fde047
400facc15
500eab308
600ca8a04
700a16207
800854d0e
900713f12
950422006
tealSupporting
50dfffff
1008ff1ff
2005cd8f9
3003bbcdd
40005a2c2
5000088a7
600006d89
70000546a
800003b4d
900002432
950000f18
purpleSupporting
50fff7ff
100ebd1ff
200d6a9ff
300c37eff
400aa5bf6
5008f3dd7
600781dbc
7006100a1
800460078
9002d0050
95015002c

Commodity colours

The deep shade carries a white label at AA or better. Underlines are 2px, washes are 9% in oklab, tiles 16%.

  • White maize --commodity-maize · #C2410CWhite label 5.2:1
  • Wheat (local and import) --commodity-wheat · #7A6937White label 5.4:1
  • Soya --commodity-soya · #5A7A1EWhite label 5.0:1
  • Soya meal --commodity-soya-meal · #8A5A36White label 5.8:1
  • Fertilizer --commodity-fertilizer · #7A3FB8White label 6.5:1

Type

Two families. Inter for everything you read. Instrument Sans condensed, always uppercase, for headline figures, commodity tabs and detail titles. Specs are size/line height · weight · tracking.

Regular is 480

A touch heavier than 400, which keeps small translucent text crisp on white. Strong body is 580, strong headings 600.

14px carries the product

body-3 is the workhorse for tables, meta lines and rows. Headings are rare; condensed figures do the shouting.

Every figure is tabular

Money, tonnes, percentages, refs and timestamps all use tabular numerals. Kwacha millions take M: K125M, K174,840.

Display

t-display-180/84 · 520 · -1px
K125M
t-display-440/44 · 600 · -0.4px
K190,790,400

Heading

t-heading-132/36 · 600 · -0.4px
Stock: Kafue Road, Lusaka
t-heading-2 strong24/28 · 600 · -0.3px
Credit book: Winter Wheat 2026
t-heading-2 regular24/28 · 540 · -0.3px
Credit book: Winter Wheat 2026
t-heading-3 strong20/24 · 600 · -0.2px
Needs you
t-heading-3 regular20/24 · 540 · -0.2px
Needs you
t-heading-4 strong18/22 · 600 · -0.2px
Coverage
t-heading-4 regular18/22 · 540 · -0.2px
Coverage

Body

t-body-1 strong16/22 · 580 · -0.16px
Amount
t-body-1 regular16/22 · 480 · -0.16px
Amount
t-body-2 strong15/20 · 580 · -0.1px
ZAM4702 short 1,400t
t-body-2 regular15/20 · 480 · -0.1px
ZAM4702 short 1,400t
t-body-3 strong14/20 · 580 · -0.1px
Kafue Valley Milling · window closes in 12 days · 2d
t-body-3 regular14/20 · 480 · -0.1px
Kafue Valley Milling · window closes in 12 days · 2d
t-body-4 strong13/16 · 580 · -0.06px
folded from 41,882 movements · to 03 Aug 2026 · 07:10
t-body-4 regular13/16 · 480 · -0.06px
folded from 41,882 movements · to 03 Aug 2026 · 07:10

Caption

t-caption-1 strong12/16 · 580 · -0.1px
Showing 4 of 72 trades · season 2026
t-caption-1 regular12/16 · 480 · -0.1px
Showing 4 of 72 trades · season 2026
t-caption-2 strong11/16 · 580 · -0.1px
Charts by TradingView
t-caption-2 regular11/16 · 480 · -0.1px
Charts by TradingView

Condensed · always uppercase

t-display-1-condensed80/76 · 700 · 0
K125M
t-display-2-condensed60/58 · 700 · 0
K11.8M
t-display-3-condensed48/48 · 700 · 0
34,117
t-display-4-condensed40/40 · 700 · 0
2,000 MT
t-heading-1-condensed32/32 · 700 · 0.3px
ZAM4702 · Kafue Valley Milling
t-heading-2-condensed24/24 · 700 · 0.1px
White maize Wheat (local) Soya
t-heading-3-condensed20/20 · 700 · 0.2px
53%
t-body-2-condensed15/15 · 700 · 0.2px
01 Critical
t-caption-1-condensed12/12 · 700 · 0.15px
Target
t-caption-2-condensed11/11 · 700 · 0.15px
Beta

Shape

A radius scale with one odd step, 14px, used only on 48px buttons. On white, shadows do the separating that grey fills would do elsewhere.

--radius-5xs2pxsmall badges
--radius-4xs4pxkeyboard hint, skeleton text, severity tag
--radius-3xs6px
--radius-2xs8pxquantity chips, selected segment
--radius-xs10px32px buttons, stepper
--radius-sm12px40px buttons, figure buttons, search, segmented track
--radius-sm-h14px48px buttons
--radius-md16pxhover rows, tiles, banners, popovers
--radius-lg20pxcards, tickets, chart cards, dialogs
--radius-xl24px
--radius-2xl32px
--radius-max999pxpills, capsules, avatars

Elevation

Six exact shadow stacks.

--shadow-flatBorder only, no lift
--shadow-minimalStepper, selected segment, carousel arrows
--shadow-minimal-softCards, tickets, decision panels, chart cards
--shadow-middleQuantity chips
--shadow-strong48px actions, menus, popovers
--shadow-dialogDialogs, bottom sheets

Motion

Springs are CSS linear() curves, so there is no animation library. Every animation has a reduced-motion fallback that keeps colour changes and drops scale, translate and ping.

0.00.51.0 050%duration peak 1.015
  • --ease-springTab underline, label and spinner swaps, toggles · overshoots to 1.015
  • --ease-expandAccordions, bottom sheets · no overshoot
  • --ease-cardCard commodity wash · 400ms in, 160ms out
ease-spring
ease-expand
ease-card
150ms--dur-defaultcolour + hover
50ms--dur-press-down3D press
100ms--dur-press-up
180ms--dur-capsuleweight swap
300ms--dur-underlinetab underline glide (spring)
500ms--dur-swaplabel / spinner swap (spring)
400ms--dur-card-in
160ms--dur-card-out
300ms--dur-sheetbottom sheet (expand)
150ms--dur-skeleton
300ms--dur-photo
1.5s--dur-ping
0.8s--dur-spin
44s--dur-marcha marching track, one full wrap
250ms--dur-splash-outthe splash fading away once the app is ready
12s--dur-splash-stuckthe splash says it is still loading
1--scale-press-button
1--scale-press-card

Components

These are the real components from project/components, running live. Every one is interactive; the commodity switch in the rail recolours the ones that take a commodity.

Buttons

Brand for entry points, primary for decisions, critical for destructive, commodity only on the trade ticket. Every button that starts work has the loading state.

Press buttons

A face on a 4px edge. It sinks 4px in 50ms and returns in 100ms. Filled edges are the fill under 15% black.

Capsules, segmented, stepper, tabs, search

Selected capsules go white with a 1px ring and a 3px halo; the label weight settles over 180ms. Tab underlines glide on the spring.

Status, trust and sync

Status marks are an icon and a word. Provenance and confirmation are separate chips. Sync health is green while live and red, without a ping, when silent.

Cards, figures and rail rows

List items carry share bars in the commodity colour, quantity chips and figure buttons. Figures open their derivation.

Data table

Dense, tabular, with ref links, a restricted cell, a derived cell and a correction row. Tables scroll inside their own container on phones.

Charts

TradingView Lightweight Charts, styled to the system: dotted grid, magnet crosshair, end-of-line labels, dashed policy lines. The TradingView logo stays visible.

Trade ticket

The main action panel: side toggle, commodity tabs, amount, quick amounts and a full-width commit in the commodity colour.

  1. 1
    Header

    28px commodity tile, context in body-3 secondary, subject in body-1 strong.

  2. 2
    Side toggle

    Two-up 48px press buttons. The chosen side fills with the commodity colour.

  3. 3
    Amount

    Label on the left, condensed figure on the right with a quaternary unit, quick amounts beneath.

  4. 4
    Commit

    48px large press button with the strong shadow. It keeps its size and spins until the trade lands.

Reconcile, evidence and history

The goods-received reconcile in its three outcomes, evidence tiles, and an append-only timeline with a compensating entry.

Screens

Interactive UI kits built only from these components, with fictional sample data. The desktop app is drawn at 1440; the mobile app, partner portal and field app at 390.

Desktop app

Command Center, Trade Desk, Warehouse, Stock, Farmer Finance

Open
Mobile app

The same screens at 390

Open
Partner portal

Counterparties confirm or dispute

Open
UAG One Link app

Field tasks, offline-first capture, PIN and enrolment

Open

Layout

A centred 1293px shell. List pages have a content column and a sticky "Needs you" rail; detail pages swap the rail for the 350px action panel. Phones get one column and a bottom tab bar.

Shell1293px max · 16px sides, 24px from 1024px
Two columnsFrom 1152px: rail 320px, 40px gap; below, the rail stacks
Wide railFrom 1280px: rail 358px
Action panel350px on detail pages · bottom sheet below 1024px
Header116px phone · 106px from 1024px · blurs on scroll
Mobile tab bar70px + safe area
Spacing4px unit · sections 24px, 32px from 1024px · cards 16px
ArtboardsDesktop 1440 · mobile 390

Cards

Every specimen card from the Claude Design export, each a standalone page. They are the source these pages are built from.

Components

One Link