Design system

Every contrast figure on this page is computed in the browser as you read it, from the same tokens the prototype runs on. Nothing here is a screenshot of a decision made elsewhere — if a token changes, this page changes with it.

Colour

Hue 279, a measured indigo. It was chosen against two reflexes rather than toward a preference: fintech defaults to navy-and-gold, and “fintech but not that” defaults to a black terminal. It also sits far from every hue that carries money meaning here, so the brand can never be mistaken for a financial state.

Ratios are against the current theme's page background. --ink-3 is the text floor — nothing lighter may ever carry a glyph, including placeholders and disabled labels. --ink-4 is for non-text and large text only.

Type

The IBM Plex superfamily, self-hosted. Plex Sans and Plex Sans Arabic are the same design intent extended to a second script — a script extension, not a font pairing. Plex Serif is a display face and appears on the showcase only; it is banned from UI labels, buttons and data.

Both scripts, same scale

English

Balance today

Your balance stays above your buffer for the whole year ahead.

العربية

الرصيد اليوم

يبقى رصيدك فوق احتياطيك طوال العام القادم.

Arabic is never letter-spaced. Tracking Arabic breaks the joins between letters — it is not a stylistic choice, it is a spelling error. Arabic also runs 20–30% longer than English, so every label has headroom.

Components

Every interactive component ships default, hover, focus, active and disabled. A component with half its states is not finished.

Buttons

Every pressable element takes scale(0.97) on :active. It is 120ms and nobody consciously notices it — that is the point. Hover states are gated behind (hover: hover) so a tap on a phone does not leave a stuck hover.

Fields

Amount cannot be negative

Placeholder text uses --ink-3, the same floor as body copy. Placeholders set two steps lighter “for elegance” are the single most common contrast failure in a design system.

Status & empty states

You run short on 16 Jan
School fees and Eid land in the same fortnight.
Below your buffer
You dip under 3,000 for eleven days.
Nothing to worry about
You stay above your buffer all year.
Pay school fees monthly
Same annual cost, spread over twelve months.

Nothing matches that

Try a different word, or clear the filters to see everything.

An empty state teaches the interface. “Nothing here” teaches nothing, and it is the state a new user sees first.

Charts

Hand-rolled inline SVG. One y-axis, never two. Horizontal gridlines only, and recessive. Text wears ink tokens, never the series colour — a coloured mark beside a label carries the identity.

The forecast treatment

Actual — solid, ink, it happened Projected — dashed, brand, it is a claim Sensitivity band Below zero

Nothing about a projection is allowed to look as certain as the past. The band is a stated sensitivity — “if variable spending runs ±6%” — widening with the square root of time, not a statistical confidence interval, and the legend says so.

Category series

Direction & motion

What mirrors

  • Layout, via logical propertiesmargin-inline, inset-inline, border-inline. There are exactly two physical overrides in the whole stylesheet, both where direction is genuinely the meaning: the drawer transform and the switch knob's travel.
  • Chevrons and navigation arrowsThey encode reading direction, so they flip. Logos, clock icons, checkmarks and media controls do not.
  • Chart chrome — but not the time axisThe value axis moves to the start edge; time still runs left to right, because numeric and temporal progression reads that way in Arabic too.
  • Numbers never reorderEvery figure is an isolated LTR run. Without that, “−20k” renders as “20k−” in an Arabic document.

Motion

150–250ms on everything in the app; users are in a task and should not wait for choreography. The one exception is the chart wipe on first paint, at 900ms, because that is the moment the product explains itself. Every animation has a prefers-reduced-motion alternative — fewer and gentler, not none.