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.
--primary--background--foreground--muted--border--destructiveInconsistency 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.
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.
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.
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.
One system, many consumers
Anything merged had to hold up beyond the screen it was first built for.
Design and code together
Components get designed and pushed as real code, not specced in Figma and handed off to be reinterpreted.
Engineers are a gate, not a bystander
Every component gets tested by engineering before it's allowed into the shared system.
Figma documents, it doesn't decide
Once a component is live, Figma gets updated to match it, rather than the other way around.
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
--primaryWhat a color means
Implied by whichever color was picked
32 semantic roles, each with a light and dark value
What a status looks like
Styled wherever the status appeared
4 tones, shared by every status in the product
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
Who can change it
Nobody owned it, so everyone did
One way in, through engineering review
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.
62 colors, 6 type sizes each locked to a line height, 7 radius steps from one base.
32 roles, from primary to border to ring, each with a light and a dark value.
Not split out yet. Each component's choices live in its variant definition.
13 components and 2 shared patterns. RFleet is built 100% on them.
--color-blue-600#1c64f2
--primaryblue-600 in light mode, blue-500 in dark.
In the variantButton's default variant reads bg-primary directly.
<Button>Every primary action in RFleet.
--color-yellow-100
--color-yellow-600No status role yetStatus skips this tier.
In the variantBadge's warning variant reads the ramp directly.
<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.
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.
Primitive. Says what it is, never what it's for.
Semantic. Every surface has a matching text color, so contrast is never a guess.
Component. The spec line and the code's props are the same three words.
Badge / warning / lg Label: Out of service
<Badge variant="warning" size="lg"> Out of service </Badge>
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.
| Tone | Meaning | Vehicle status | Document status |
|---|---|---|---|
| success | Good to go | Available | Approved |
| neutral | Normal, no action | In use | |
| warning | Needs attention | Out of service | In review |
| danger | Act now | Stolen | Rejected |
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 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.
- 13 components
- ButtonBadgeCardInputCheckboxSwitchDialogDrawerAvatarTooltipSeparatorBreadcrumbLabel
- 2 shared patterns
- Filter chipPlate select
- Every control ships with
- hoverfocus-visibledisabledinvalid
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.
- Need
A screen needs something the system doesn't have, or an existing component falls short.
- Design in code
I design the component and push it as real code, with its variants and states.
- Engineering gate
Engineers test it before it can join the shared system.
- Merge and review
It's merged into the shared library and reviewed in Storybook.
- Document
Figma is updated to match what shipped, not the other way around.
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.
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.
- 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
- Engineers reach for the shared library more often than they build one-off components, based on team feedback rather than a tracked metric
- A measured design-to-dev handoff time reduction
- A measured change in QA issues tied to UI inconsistency
- Adoption tracking across every Reviver team