Skip to content

Case study

One design system, every brand

How it was built, what the numbers are, and what they do not cover.

Opening

The Thursday problem

Placeholder — Anuj writes this

A concrete moment: a new client signs on Thursday and their branded app has to be in pilot on Monday. One paragraph, first person, no system vocabulary.

Try it

Type a colour. Get a brand.

Six inputs become a light and dark theme that passes WCAG 2.2 AA. Every correction the solver made is listed, so you can see where your colour was and where it had to go.

Brand inputs

Start from
Neobank
Insurance
بقالة ومكافآت
Family health benefits
रीसेलर कॉमर्स
This site
Accent colour
Neutrals
Shape
Density
Type pair

Live preview

After fees

Net revenue

$172,250+24.8%vs the 4 weeks before
  • This period
  • Previous period

28 points, Sep 3 to Sep 30. This period: low $4,390, high $7,820, last $7,820. Previous period: low $4,020, high $5,740, last $5,740.

This period, Previous period
DateThis periodPrevious period
Sep 3$4,390$4,110
Sep 4$4,720$4,020
Sep 5$4,960$4,290
Sep 6$5,210$4,380
Sep 7$4,870$4,210
Sep 8$5,030$4,460
Sep 9$5,390$4,520
Sep 10$5,620$4,390
Sep 11$5,480$4,610
Sep 12$5,810$4,730
Sep 13$6,040$4,680
Sep 14$5,720$4,820
Sep 15$5,960$4,910
Sep 16$6,210$4,760
Sep 17$6,480$4,990
Sep 18$6,130$5,120
Sep 19$6,390$5,040
Sep 20$6,670$5,210
Sep 21$6,420$5,170
Sep 22$6,810$5,330
Sep 23$7,040$5,290
Sep 24$6,720$5,460
Sep 25$7,110$5,510
Sep 26$7,380$5,380
Sep 27$7,020$5,620
Sep 28$7,290$5,690
Sep 29$7,560$5,580
Sep 30$7,820$5,740

September by channel

Card payments
$28,460
+8.4% betterversus August
Bank transfers
$9,870
+3.1% betterversus August
Refunds
$1,240
−12% betterversus August
New

Get paid the day you invoice

Instant payouts move card sales into Operating within minutes, for 1% a payout.

  • Minutes, not two daysCard sales land as soon as they settle.
  • Same checks as todayEvery payout still goes through review.
  • 1% a payout, capped at $15Only on the payouts you speed up.

Could have landed early in September

$28,460

Accounts

$289,690 across 5 accounts

  • OperatingChecking ·· 4821$84,250+5.2%
  • Tax reserveSavings ·· 3306$32,600-6.1%
  • Reserve fundTreasury · 4.1% yield$124,800+3.4%
  • Card salesSettling · lands Oct 1$6,840+12.6%
  • PayrollChecking ·· 7730$41,200+2.1%

Spend limits

Team cards reset on October 1.

Team cards, $5,000 a month$3,120 spent$1,880 left
Free transfers, 25 a month18 used7 left

Approvals

Payouts over $2,500 need a yes from one of them.

Maya LindqvistTomás ReyesInes Adeyemi

Scheduled

3 payments go out in October.

Payment schedule, October 2026

27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Payroll, Oct 2−$38,420.00

Pay a supplier

From Operating ·· 4821

Pay to
Arrives

Activity

Across all accounts, newest first.

  • Payout to Lumen Print Co.Today, 09:42−$4,800.00Needs approval
  • Invoice #1042 paidToday, 08:15+$12,650.00Completed
  • Card charge at Northline AirYesterday−$612.40Declined
  • Payroll, 14 peopleFri, Oct 2−$38,420.00Scheduled

Alerts

Push and email, as it happens.

Before anything over $2,500 leaves an account.
When Operating drops under $10,000.
A team card is declined, with the reason.
Every Monday: what came in, what went out.

Same code

Three brands, one component library

A brand has to be data, not code. Nothing below differs in code, and no component ever learns which brand it is rendering — there is no tenant id anywhere in packages/react.

  • Vela, Neobank, in light mode
    VelaNeobanken-IN
  • Harbor, Insurance, in light mode
    HarborInsuranceen-GB
  • Qamar, بقالة ومكافآت, in light mode
    Qamarبقالة ومكافآتar-AE-u-nu-arab · RTL

Evidence

Numbers, and how to reproduce them

A design system that claims accessibility and cannot show it is a brochure. Each figure is read at build time from the file its command writes — none is typed by hand.

  • 100%Contrast checks that pass118,000 checks across 1,000 random brands, 0 failurespnpm test:themes
  • 6Inputs that drive a brand6 brands ship this way, each one file; no file under packages/react changes to add onecat tenants/<id>/brand.json
  • 64% → 88%Agent runs fully on-systemclaude-sonnet-5, 100 runs: no context, then MCP + AGENTS.mdnode evals/report.mjs --iteration 2
  • 99.8 / 100Drift score, reference blocks1,150 places checked in 8,003 lines, 1 findingpnpm drift apps/docs/blocks
  • 58Componentseach with a meta.json that drives its docs page, the MCP server and the SDUI schemapnpm check:meta

Judgement

Five decisions that mattered

Every architectural call is an ADR, and every ADR records who made it — Anuj, the agent recommending and Anuj accepting, or the agent alone pending review. There are 48, and not one is anonymous. 6 are still waiting on his review, and they say so.

  1. ADR-006

    Let a brand pick its colours, or guarantee the result is legible. You cannot promise both.

    Call. Generate the ramps in OKLCH and run a solver that moves a role until the pair passes — then record what it moved.

    Consequence. A brand can hand over any hex. Nothing ships that fails, and the page shows the correction rather than hiding it.

  2. ADR-011

    Copy-in components get adopted fastest. Versioned packages are the only way to deprecate anything.

    Call. Built both from one source, shipped both — then withdrew the registry four weeks later and kept npm.

    Consequence. One install route to document and test. The reversal is in the ADR as a revision, not a rewrite.

  3. ADR-012

    Overlays portal to the end of the document, so they leave the element whose brand and direction they belong to.

    Call. Every overlay copies the nearest scope’s data attributes, dir and lang as it opens; the locale drives direction, not a dir prop.

    Consequence. A dialog opened from an Arabic tenant is Arabic. The helper is repeated in each overlay file so a copied file still works.

  4. ADR-021

    Button’s danger was a variant, which put status in the same prop as emphasis. Fixing it breaks everyone using it.

    Call. Deprecate in a minor, remove at 1.0 only, ship a codemod with the RFC, and warn in development.

    Consequence. The governance claim has one real deprecation behind it instead of a policy document.

  5. ADR-019

    Every design system is asked for native components. Building them badly is worse than not building them.

    Call. Said no. Shipped a server-driven UI schema and native token files instead, and wrote down that there are no native components.

    Consequence. The mobile claim is small and true. Scope held.

In practice

Rolling this out to 40 designers and 300 engineers

  1. Q1

    Audit, and earn the right

    Inventory what exists across the estate and count it, not eyeball it. Pick the ten components that appear everywhere. Publish the audit before proposing anything.

    Measured by. Baseline: components in use, duplicates per pattern, contrast failures per surface.

  2. Q2

    Foundations, with one team

    Tokens and the theme engine, adopted by a single product team who ship with it. No library-wide rollout until one real surface runs on it.

    Measured by. That team’s drift score, and the time it takes them to re-skin.

  3. Q3

    The top ten, and the governance that keeps them

    Ship the ten, with RFCs, deprecations and codemods from day one. A design system without a deprecation path becomes a fork within a year.

    Measured by. Adoption per component, and the number of overrides written against them.

  4. Q4

    Adoption as a programme, not a memo

    Office hours, a contribution path, and a lint rule that suggests the right component rather than only naming the wrong one. Measure the system, not the designers.

    Measured by. Drift score across the estate, and how many teams ship without asking us anything.

Limits

What this does not prove

  • No users. Nobody has adopted this. Every number measures the system against itself, not a team against a deadline.
  • One maintainer. The governance is real and runnable, but it has never survived two people disagreeing about a breaking change.
  • RTL is table stakes now. shadcn/ui calls its own RTL first-class and Untitled UI React ships logical properties throughout. Doing it from day one was right; it is not a difference.
  • Tenant copy is mine, not a native speaker’s. The Hindi and Arabic are marked as drafts in the interface wherever they appear, and stay marked until someone fluent reads them.
  • The agent eval is one model, one task family, 100 runs. It shows a direction, not a law. Its first run reported 0% against 70%, which would have been a far better headline; it was a fault in the harness. Both runs are in the repository and the invalid one is still there, marked. A number you cannot reproduce is worth less than no number.