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