← All work

Reviver Had UI Elements. It Didn't Have a Design System.

The inconsistency wasn't a styling problem. The same decisions (which color, what a status means, how a button behaves) were being made again on every screen. I led the initiative to make each one once: a tiered token system, a shared library, and one way in that product and engineering both work from.

Role
Lead Designer, initiative owner
Company
Reviver
Time Frame
Four Weeks
100%
of RFleet built on the system, which is also used in another Reviver product
1 source
of truth in code, reviewed in Storybook, with Figma as documentation
107 tokens
62 primitive colors, type and radius scales, and 32 semantic roles with dark mode
4 tones
carry every status in the product, so the same meaning always looks the same
Primitive tokens
gray
blue
red
yellow
green
indigo
Semantic tokens light / dark
--primary
--background
--foreground
--muted
--border
--destructive
Components
Assign RplateCancel
AvailableIn use
Out of serviceStolen
Stolen 32
Rendered from the system's real token values.
01
Where it started
UI elements without a system, and who was paying for it.

Inconsistency was costing both sides of the team

Reviver's product had basic UI elements: buttons, inputs, cards. What it didn't have was a design system that scaled. The same component existed in slightly different versions across the product, and neither design nor engineering had one shared source of truth to point to when a screen didn't match another.

Product paid for it in meaning

In a fleet product, color carries information. When the same kind of status could look different from one screen to the next, color stopped being a signal anyone could rely on.

Engineers paid for it in duplicated work

Without a shared source of truth, the same component got rebuilt slightly differently by whoever needed it next, which meant repeated work and steady drift.

Nobody could point to one true version

There was no shared reference for what a button, a card or a status color was supposed to be, so specs got argued over case by case instead of settled once.

02
What I took on
The initiative, and the constraints that bounded it.

The system had to live in code, not just in Figma

I started the initiative and set a different model from the static handoff that caused the drift. I design components and push them as real code, engineers test them, and once they pass they're merged into the shared system and reviewed in Storybook. Figma documents what shipped.

Constraint

The old handoff caused the drift

Static screens reinterpreted by each engineer had produced the inconsistency, so the fix couldn't run on the same model.

Constraint

Four-week window

The system had to prove itself fast, which meant prioritizing the components causing the most drift over full breadth on day one.

Constraint

One system, many consumers

Anything merged had to hold up beyond the screen it was first built for.

  1. Design and code together

    Components get designed and pushed as real code, not specced in Figma and handed off to be reinterpreted.

  2. Engineers are a gate, not a bystander

    Every component gets tested by engineering before it's allowed into the shared system.

  3. Figma documents, it doesn't decide

    Once a component is live, Figma gets updated to match it, rather than the other way around.

03
The system
The decisions it makes once, and how it stays one system.

Every inconsistency was a decision being made per screen

Restyling screens wouldn't have fixed this. The drift came from the same handful of decisions being made again, slightly differently, every time someone built something. So I designed the system around those decisions: make each one once, in a place design and engineering both read from.

Which color

Picked by whoever built the screen

62 primitive colors, named by hue and step

--primary

What a color means

Implied by whichever color was picked

32 semantic roles, each with a light and dark value

AvailableIn useOut of serviceStolen

What a status looks like

Styled wherever the status appeared

4 tones, shared by every status in the product

DefaultOutlineGhost

How a component works

Rebuilt by whoever needed it next

13 components with defined variants and states

Badge / warning→variant="warning"

What a spec means

Reinterpreted at handoff

Names that are identical in the spec and the code

Engineering gate

Who can change it

Nobody owned it, so everyone did

One way in, through engineering review

Tokens

Four tiers, each with one job

The token model separates what a value is from what it means and where it's used. Products only touch components, so changing what "primary" means is one edit, not a search across screens. RFleet runs on the first two tiers and the last one in full. The third is where the system is thinnest today.

Primitive tokensRaw values

62 colors, 6 type sizes each locked to a line height, 7 radius steps from one base.

Semantic tokensMeaning, not value

32 roles, from primary to border to ring, each with a light and a dark value.

Component tokensScoped to one part

Not split out yet. Each component's choices live in its variant definition.

ComponentBuilt from tokens

13 components and 2 shared patterns. RFleet is built 100% on them.

Brand action, traced through the tiers
Primitive--color-blue-600

#1c64f2

Semantic--primary

blue-600 in light mode, blue-500 in dark.

Component tokenIn the variant

Button's default variant reads bg-primary directly.

Component
Assign Rplate
<Button>

Every primary action in RFleet.

Needs attention, traced through the tiers
Primitive--color-yellow-100
--color-yellow-600
SemanticNo status role yet

Status skips this tier.

Component tokenIn the variant

Badge's warning variant reads the ramp directly.

Component
Out of service
<Badge variant="warning">

Also used for In review.

Both traces come from RFleet's theme file and component source. Semantic tokens hold the same value as their primitive rather than referencing it, so --primary and blue-600 are kept in step by hand today. Token count: 62 + 6 + 7 + 32 = 107, counting each type size and its line height as one token.

Naming

One vocabulary for design and engineering

The old handoff drifted because a spec and the code described the same thing in different words. I named every token in the Tailwind and shadcn/ui conventions the engineers already wrote every day, so a token's name is the class they type and a spec reads like the code it becomes.

--colorcategorybluehue600step

Primitive. Says what it is, never what it's for.

--mutedrole-foregroundtext on it

Semantic. Every surface has a matching text color, so contrast is never a guess.

Badgecomponentwarningvariantlgsize

Component. The spec line and the code's props are the same three words.

What the spec says
Badge / warning / lg
Label: Out of service
What ships
<Badge variant="warning" size="lg">
  Out of service
</Badge>
What users see
Out of service
Usage rules

One meaning per status, across the product

Status is where the old inconsistency hurt most, because color there carries meaning. The system maps every status to one of four tones, so a vehicle status and a document status that mean the same thing look the same.

ToneMeaningVehicle statusDocument status
successGood to goAvailableApproved
neutralNormal, no actionIn use 
warningNeeds attentionOut of serviceIn review
dangerAct nowStolenRejected

Tokens, never raw values

Products pick a token or a variant. A hex code in product code is a bug, because it's the start of the next fork.

Extend, don't fork

A new need becomes a new variant on the shared component, reviewed and merged, not a local copy.

States are part of the component

Focus, disabled and invalid ship with every control, so accessibility isn't left to each screen.

Components

Components follow from those decisions

With color, meaning and naming settled, a component only has to define structure and states. That's what kept the library small enough to build in four weeks. The plate select shows the single-source idea best: one component runs in the vehicles table and inside Ask's answer cards, with no second version.

Button
Buttons: Default, Outline, Secondary, Ghost, Destructive, Link and Disabled
Status badge
Status badges: Available, In use, Out of service, Stolen
Filter chip
Filter chips: Stolen 32 active, Hardware Issues 19 inactive, Registration Renewal 71 with a dropdown
The libraryRendered from the real components
13 components
ButtonBadgeCardInputCheckboxSwitchDialogDrawerAvatarTooltipSeparatorBreadcrumbLabel
2 shared patterns
Filter chipPlate select
Every control ships with
hoverfocus-visibledisabledinvalid
Governance

One way in keeps it one system

A system without an owner drifts the same way the old UI did. Every addition takes the same path, and nothing reaches a product without passing engineering. Claude Code, as my implementation partner, is what made designing in code practical: each component got tested in a running product, not a mockup.

  1. Need

    A screen needs something the system doesn't have, or an existing component falls short.

  2. Design in code

    I design the component and push it as real code, with its variants and states.

  3. Engineering gate

    Engineers test it before it can join the shared system.

  4. Merge and review

    It's merged into the shared library and reviewed in Storybook.

  5. Document

    Figma is updated to match what shipped, not the other way around.

Trade-offs

What I chose, and what it cost

Build on shadcn/ui, not from scratch

A four-week window meant starting from proven, accessible primitives and spending the time on Reviver's tokens and variants. The cost: the system inherits that library's structure.

Tokens in one app theme, not a package yet

Keeping tokens in one Tailwind theme got RFleet fully on the system fast. The cost: sharing it more widely means extracting the tokens into a package.

Two tiers first, component tokens later

Primitives and semantic roles cover brand, surfaces, text and borders, which is where dark mode and rebrands hit. The cost: status skips the semantic tier, components hold their choices in variants instead of component tokens, and the vehicle map repeats four status colors as raw hex because its marker layer takes plain values.

04
Where it landed
What changed, and what's still just described.

Product and engineering now work from the same source

The clearest change is in how the work is divided. Product focuses on flow, usability and edge cases; engineers clean up implementation and merge the final version into the shared system. That split didn't exist before, when every handoff was a fresh negotiation over what a component was supposed to be.

Observed
  • RFleet is built 100% on the system's tokens and components
  • The system is also used in another Reviver product
  • Design ships as reviewed, merged code, not a spec that gets reinterpreted
  • Vehicle and document statuses share one tone mapping
  • Tokens run in tiers: 62 primitive colors and 32 semantic roles with dark mode, named as the classes engineers write
Estimated
  • Engineers reach for the shared library more often than they build one-off components, based on team feedback rather than a tracked metric
Not yet evidence
  • A measured design-to-dev handoff time reduction
  • A measured change in QA issues tied to UI inconsistency
  • Adoption tracking across every Reviver team

Ready to Start Your Project?

Let's bring your design vision to life.

Schedule a Call