01 / 07 · Case studySaaS

One design system shared across three products.

Year
2025–2026
Role
Sole Designer
Team
Product, Engineering
Industry
Enterprise SaaS
Contribution
Tokens, component library, accessibility standards, documentation, implementation review
Hear me narrate this case study instead4:06 · In my own voice

One system for three products

I built the design system behind Sciene’s Companion: the tokens, the component library, the accessibility standards and the documentation. Engineering built it in React and Tailwind. I didn’t write the production code, but I reviewed the implementation with them until design and code matched.

  • 3× faster to ship a feature, comparing delivery time before and after the system
  • 100% token parity between Figma and code
  • 50+ components shared across three products
  • 0 screens redesigned to ship the v2 Director Dashboard
The design system in Figma: button specs and variants, calendar, avatar, badges, tabs, banners, button group and icon buttons
Tokens and components shared by the three Companion modules.

Three products, three versions of everything

Account Flagging, the Email Hub and the Meeting Hub each solved the same button, status and layout on their own. On top of legacy code, that meant slower builds, inconsistent screens and a steady stream of bugs.

Sciene Meeting Hub before the design system, with client data blurred
  1. Two button languages in one toolbar. An outline action next to a filled one, neither clearly primary.
  2. The active nav item is a solid blue bar, while other rows change text colour only on hover.
  3. A third blue, in a third shape, for the company badge.
  4. Every metric card carries its own border and padding, set per screen rather than from a shared card.
One Meeting Hub screen, with every pattern that was solved twice marked.

Built alongside v1, not before it

Engineering was already shipping, so the system couldn’t pause the product. I built it alongside v1 and adjusted components as we delivered.

  1. FoundationsColour ramp, type scale, spacing steps, radii. Decided once, argued about once.
  2. Semantic tokensRoles rather than values. btn-primary-bg, btn-secondary-bg, bg-secondary, and the severity roles behind critical, warning, and healthy.
  3. PrimitivesButton, input, select, checkbox, badge. No product knowledge in them at all.
  4. ComponentsThe alert row, the filter bar, the metric card. Primitives composed into things that know a little about the domain.
  5. PatternsTriage list, acknowledgement flow, empty and loading behaviour. The rules for how components sit together.
  6. ProductAccount Flagging, the AI Email Hub, the Meeting Hub. Assembled from the layers above, holding only what is genuinely specific to them.
  1. Raw value#f2f4f7
  2. Semantic rolebtn-secondary-bg
  3. PrimitiveSecondary button
  4. ProductSnooze, on an alert a CSM is triaging
A value changes once and reaches every screen.

Foundations every component inherits

Components never pick a raw value. Colour, type, radius and elevation are tokens: primitives first, then semantic names on top, so changing one updates all three products.

Brand

  1. 50
  2. 100
  3. 200
  4. 300
  5. 400
  6. 500
  7. 600
  8. 700
  9. 800
  10. 900
  11. 950

Neutral

  1. 50
  2. 100
  3. 200
  4. 300
  5. 400
  6. 500
  7. 600
  8. 700
  9. 800
  10. 900
  11. 950

Severity, as semantic tokens

  • Criticalfg-errorbg-error-primary
  • Warningfg-warningbg-warning-primary
  • Healthyfg-successbg-success-primary
A slice of each token set. Primitives sit underneath; components only ever use the semantic names.

Severity is decided once, in the tokens

Critical, warning and healthy appear in all three modules. I rejected per-product palettes, because the same red has to mean the same thing in the Email Hub as in Account Flagging. Each severity became a token with a fixed colour, icon and weight. Severity never relies on colour alone.

Short term total down 40% week over weekCriticalHarbor & Pine Home · 20 hours ago
Token
severity.critical
Colour
error
Icon
octagon-alert
Weight
500

Critical. Colour error, icon octagon-alert, weight 500

One alert row. Each severity is a token with a fixed colour, icon and weight.

Secondary buttons fill, and so does hover

Outline buttons disappeared next to dense tables and read as decoration. I replaced them with filled secondary buttons, and made every hover a fill instead of a colour change, which is easy to miss while scanning forty accounts.

AccountOpen flagsLast contactAction
2TodayView account
3YesterdayView account
13 days agoView account
0Last weekView account
Before: outline buttons and colour-only hover. After: filled buttons and fill hover.

Docs written for engineers and their AI assistants

Figma-only notes left engineers guessing. I wrote the docs as usage rules with semantic token names, so a person or an assistant asking for an alert row picks the right component. Consistent AI output took iteration: I built skills that encode the rules and kept refining them until the output held.

severity.critical

Meaning
An account needs action today
Use for
Alerts that block revenue or renewal
Don’t use for
Errors in a form; use the field error state
Pairs with
Alert icon, text label

Produces

Critical
Sales dropped 32%Harbor & Pine Home · 2 hours ago
Rules carry the meaning, not just the colour. That’s what keeps an assistant on the right component.

The Director Dashboard came from parts we already had

By v2, someone could build a screen I’d never drawn and it still looked like Companion. The Director Dashboard shipped without a single new screen designed from scratch.

Validation shows up as you typeUse at least 3 characters
Severity
Any severity
Critical
Warning
Healthy
Arrow keys and Escape work too
Buttons
Try them
The primitives running. Tab through for focus rings, type to trigger validation, and open the select with the keyboard.

What I’d change

I componentized too early. Things that looked like patterns after two uses turned out to be coincidences, and I folded several back into their screens. Now I wait for the third use before promoting anything.