To all worksCase study

Zenco

The design system Soraban runs on: tokens, components and the conventions that hold four products together, built from zero as the sole designer over four years.

Team
One designer, two engineers consuming the library
Scope
Four products (Collect, Prepare, Connect, Deliver) plus marketing
Role
Sole owner: tokens, components, documentation, publish cadence
Stack
Figma variables and modes, published library, Storybook mirror

When I joined Soraban the product existed, but there was nothing shared underneath it. No variables, no published library, one loose set of color styles applied inconsistently across a UI that had grown organically for a year. Every new feature started from a slightly different set of primitives, and nobody had the authority to decide which was correct.

That's how Zenco, the design system, came to be. It started as a small local set of color variables, and over four years, single-handedly, it grew into the one source of truth for the whole product. This case walks through the decisions that worked, and the one thing I'd change first if I started over.

5,148
Component variants46 UI sets
184
Variablesacross 4 collections
23,386
Bindingsresolved in the audit
37
Doc pagesone per component area
Components from the Zenco library laid out together: banners, toast, inputs, dropdowns, menus, tabs, tags, tooltips, avatars and buttons
Fig. 1 - The library, out of context. Banners, menus, inputs, tags, tooltips, pagination and avatars pulled out of the library and shown on their own, without any screen around them.

Decision log

A two-tier color chain, and the discipline to never break it

The single decision that shaped everything else was how to structure color. Most systems fall into one of two failure modes. They expose raw palette values to designers, so a rebrand means hunting hex codes across a hundred components. Or they build a semantic layer and then leak past it whenever someone is in a hurry. I wanted a chain where the semantic layer was the only address a component could bind to.

  1. A

    One flat collection of named tokens

    Rejected

    Simplest to author: everything lives in Color, named Primary/Default, with hex values baked in. Every retune means editing every token, and there is no way to swap palettes without rewriting the collection.

  2. B

    A theme provider in code, no Figma layer

    Rejected

    Push the semantic step to engineering and let Figma stay palette-only. Cleanest technically, but it splits the source of truth: designers reason in palette values, engineers in tokens, and every review becomes a translation.

  3. C

    Two collections, alias-linked, scope-restricted

    Shipped

    Palette holds 63 raw values. Color holds 101 semantic tokens, each an alias of a palette entry, with Light and Dark modes. Every semantic token is locked to a scope, so a designer physically cannot pick a border color for a background.

Palette63 raw values, 1 mode

  • Blue/Main
  • Blue/100
  • White/Main
  • Black/Main
  • Green/Main

Color101 aliased tokens, Light and Dark

  • Surface/Accent Blue/Main
  • Surface/Accent-Subtle Blue/100
  • Surface/Primary White · Black
  • Surface/Success Green/Main

Components8,332 bindings, 0 bypass

  • Toast · Info / Strong
  • Toast · Info / Subtle
  • Modal surface (Light)
  • Toast · Success / Strong
Fig. 2 - Token flow. The chain from raw palette through semantic tokens to component properties, with the Light and Dark switch living at the semantic tier. Drawn from the audit: 0 of 8,332 color bindings reach past the semantic layer.

Foundations

Making the wrong token physically unpickable

Problem
Systems drift when nothing stops a designer, mid-sprint, from using a text color on a background because the value looks right. The intent of the token is lost the moment it lands on the wrong property.
Decision
Every semantic token carries a Figma scope. Text tokens appear only in text-fill pickers, Border only in stroke pickers, Icon only in icon fills, Surface only in frame and shape fills. The picker enforces the taxonomy.
Outcome
The “designer used Border/Primary on a background” class of drift stopped being a review conversation. It stopped being possible to make the mistake.
Two Figma property pickers on the same toast component: a text-fill picker listing only Text tokens, and a stroke picker listing only Border tokens
Fig. 3 - Same picker, different property. A text-fill picker against a stroke picker on the same component: the list differs because the scope differs.

Foundations

Dark mode as a structural property, not a second theme

Problem
Dark mode in most systems is a parallel theme built by hand: a second set of tokens shadowing the first, kept in sync by discipline, drifting the moment a new color ships in one and not the other.
Decision
The Color collection has two modes. All 101 tokens are aliases into Palette with the Dark alias set once per token, so a component built from semantic tokens gets its Dark treatment the moment it lands in a Dark frame.
Outcome
No second component set for Dark, no per-screen review of what color becomes what. Adding a semantic token is one action that ships both modes.
Zenco components shown side by side in Light and Dark mode, same masters
One component, two modes. Toast, Button, Tag and Text input in Light and Dark from the same masters, resolved through the alias chain.

Convention

A page template every component gets, without exception

Problem
With one designer and no dedicated review time, documentation is the first thing to decay. Components end up documented differently, and after a year nobody, the author included, can find the canonical variant of a master.
Decision
Every component page follows the same three parts: a Master section holding the source of truth, a Variants and states frame showing the full matrix, and an Overview frame with usage and anatomy. A small set of documentation components makes the template mechanical to apply.
Outcome
26 of 28 component pages follow the template. A component is not done until it has all three parts, so documentation ships with the component instead of trailing it by a month.
Overview frame for the Toast component page, showing usage and anatomy
The Overview frame. Usage and anatomy documented before a designer ever reaches the variants below it.
Canonical Zenco component page for Toast, Figma documentation template
The template applied to Toast. Overview, then variants and states, then master. The same three blocks in the same order on every page in the library.
Figma properties panel for the Dropdown component, showing its built-in toggles and settings
One component, every setting built in. Toggles and properties adjust it exactly as needed, without multiplying tokens or components.

Health snapshot

Where the system is mature, and where the trade-offs are

I ran a full architectural audit of the library at the four-year mark, resolving all 23,386 variable bindings back to their sources. The color layer does exactly what it was designed to do. The dimensional layers, spacing and radius and elevation, are behind, and I know why.

  • Color token architectureTwo-tier alias chain, 0 of 8,332 bindings bypass the semantic layerA
  • Auto-layout adoptionAbout 99% of frames on component pages use auto-layoutA
  • Typography98 to 100% style adoption, styles only, no responsive tierA
  • Spacing tokens15 primitives, no semantic tier, doubles as the radius scaleB
  • Radius as its own layerDoes not exist: 7,894 radius bindings resolve through SpacingD
  • Elevation4 effect styles, not variable-backed, cannot mode-switch with DarkC

What I would change first

The one thing I would separate on day one

I would give radius its own token collection from the start. In Zenco every corner radius resolves through the Spacing scale, because early on, when the system was small, one 4/8/12/16 scale was clearly enough for both. It stayed that way as the system grew, and now 7,894 radius bindings point at Spacing tokens.

The practical consequence is that the two visual languages cannot be retuned independently. Retighten the spacing rhythm and the corner radius of every surface in every product moves with it. That is fine with one designer and one product. It becomes a real constraint at four products, and at the point where a design engineering pipeline needs to export tokens to code.

The migration itself is not hard: create a Radius collection, rebind those 7,894 references, keep the pixel values identical. But it has to happen before anyone builds against the exported tokens. In the current architecture it is a lift. In a fresh system it is a first-day decision that costs nothing.

TodayOne scale, two jobs · 15 tokens

  • Size/Size-01 2
  • Size/Size-03 6
  • Size/Size-04 8
  • Size/Size-06 16

padding, gap and border-radius all resolve here. 7,894 radius bindings are tied to Spacing, so neither language can be retuned alone.

SeparatedTwo scales, two intents

  • space/4 4 px
  • space/8 8 px
  • radius/sm 4
  • radius/full 999

padding and gap resolve through space, border-radius through radius. The two languages retune independently.

Fig. 7 - The coupling I would fix. Left, today's shared Size scale carrying both spacing and radius. Right, two collections: one for layout rhythm, one for surface geometry.

Zenco was built alongside the product, not for it. The system and the redesign of Soraban happened over the same four years, by the same person, each informing the other. What worked in the system shaped what shipped in the product, and what broke in the product got fixed by adding to the system.

Numbers throughout this case come from that same audit.