
Overview
Overview
Overview
At Clevrr AI, an early-stage revenue-intelligence product, colour, type, and elevation lived as scattered local styles with nothing connecting them. Nothing was addressable by name, so every screen drifted independently, and engineering came back on each handoff asking for the exact value of something I’d already decided.
At Clevrr AI, an early-stage revenue-intelligence product, colour, type, and elevation lived as scattered local styles with nothing connecting them. Nothing was addressable by name, so every screen drifted independently, and engineering came back on each handoff asking for the exact value of something I’d already decided.
A redesign forced the cleanup. Over 15 days I audited the full product, roughly 20 screens, and rebuilt the foundation as 163 semantically named tokens across colour, typography, and shadows, plus 20 text and effect styles.
A redesign forced the cleanup. Over 15 days I audited the full product, roughly 20 screens, and rebuilt the foundation as 163 semantically named tokens across colour, typography, and shadows, plus 20 text and effect styles.
Then I tested whether the structure could do more than keep designers consistent: could a model read it and generate the engineering token file directly? It could, but only once the naming carried meaning a machine could resolve. surface/static/neutral/hover maps cleanly to a CSS variable. Gray 300 doesn’t.
Then I tested whether the structure could do more than keep designers consistent: could a model read it and generate the engineering token file directly? It could, but only once the naming carried meaning a machine could resolve. surface/static/neutral/hover maps cleanly to a CSS variable. Gray 300 doesn’t.
The outcome was a foundation with two consumers: designers building screens, and a model translating those decisions into code engineering could drop in.
The outcome was a foundation with two consumers: designers building screens, and a model translating those decisions into code engineering could drop in.
Context and my role
Context and my role
Context and my role
Clevrr AI is a revenue-intelligence platform for commerce teams. It watches sales, ad spend, creative performance, and customer data daily, and surfaces revenue leaks before they reach the P&L, which in practice means a dense, data-heavy interface: charts, tables, alerts, and a lot of state.
Clevrr AI is a revenue-intelligence platform for commerce teams. It watches sales, ad spend, creative performance, and customer data daily, and surfaces revenue leaks before they reach the P&L, which in practice means a dense, data-heavy interface: charts, tables, alerts, and a lot of state.
I was the sole designer, working with a team of three to five engineers. There was no existing design system, no documentation, and no prior convention to inherit. The product had grown through scattered local styles, each one a decision made once and never connected to anything else.
I was the sole designer, working with a team of three to five engineers. There was no existing design system, no documentation, and no prior convention to inherit. The product had grown through scattered local styles, each one a decision made once and never connected to anything else.
That shaped how I approached it. With one designer supporting four engineers, I wasn’t going to scale by being more responsive at handoff; the loop itself had to get shorter. And with no precedent to follow, every naming and structural decision was mine to make, which turned out to matter more than I expected once a model, rather than a person, had to read them.
That shaped how I approached it. With one designer supporting four engineers, I wasn’t going to scale by being more responsive at handoff; the loop itself had to get shorter. And with no precedent to follow, every naming and structural decision was mine to make, which turned out to matter more than I expected once a model, rather than a person, had to read them.

The problem
The problem
The problem
Every visual decision in the product was unnamed.
Every visual decision in the product was unnamed.
Colour, type, elevation, spacing: all of it existed as local styles applied screen by screen, with nothing tying one instance to another. A value chosen for one card had no relationship to the same value on the next screen. It was a decision made once, in place, and then lost.
Colour, type, elevation, spacing: all of it existed as local styles applied screen by screen, with nothing tying one instance to another. A value chosen for one card had no relationship to the same value on the next screen. It was a decision made once, in place, and then lost.
The consequence was that the only way to transfer a design decision was to ask. Engineers came back on every handoff for hex codes, font sizes, line heights, padding values, and constantly for the question I could never answer quickly: which grey is this one? Not because anyone was careless, but because there was no name to hand over. Unnamed values can only travel by conversation.
The consequence was that the only way to transfer a design decision was to ask. Engineers came back on every handoff for hex codes, font sizes, line heights, padding values, and constantly for the question I could never answer quickly: which grey is this one? Not because anyone was careless, but because there was no name to hand over. Unnamed values can only travel by conversation.
That produced two visible failures. The built UI drifted from the designs, and spacing rhythm was inconsistent across the product; no shared scale meant every implementation made its own call.
That produced two visible failures. The built UI drifted from the designs, and spacing rhythm was inconsistent across the product; no shared scale meant every implementation made its own call.
And it produced two invisible ones, which were the expensive part: rework cycles in QA to reconcile built screens against designs, and features shipping slower because a build wasn’t done when it was built. It was done when it had been corrected.
And it produced two invisible ones, which were the expensive part: rework cycles in QA to reconcile built screens against designs, and features shipping slower because a build wasn’t done when it was built. It was done when it had been corrected.
With one designer and up to five engineers, this was structural. I could answer faster, but I couldn’t answer less. The bottleneck wasn’t my responsiveness; it was that the system required me at all.
With one designer and up to five engineers, this was structural. I could answer faster, but I couldn’t answer less. The bottleneck wasn’t my responsiveness; it was that the system required me at all.
Research
Research
Research
I audited the product manually, screen by screen, listing every colour, type, and elevation value in use. About twenty screens. No plugin for a surface this size; extraction would have given me an inventory when what I needed was judgment about which values were actually the same decision.
I audited the product manually, screen by screen, listing every colour, type, and elevation value in use. About twenty screens. No plugin for a surface this size; extraction would have given me an inventory when what I needed was judgment about which values were actually the same decision.
Two findings shaped everything that followed.
Two findings shaped everything that followed.
The same colour was doing different jobs. One hex appeared as a border on one screen and a disabled state on another. Visually identical, semantically unrelated. Which meant a palette organised by hue, the obvious way to structure this, could never be correct. Two different decisions that happen to render the same colour need two different names, or the system encodes an accident as a rule.
The same colour was doing different jobs. One hex appeared as a border on one screen and a disabled state on another. Visually identical, semantically unrelated. Which meant a palette organised by hue, the obvious way to structure this, could never be correct. Two different decisions that happen to render the same colour need two different names, or the system encodes an accident as a rule.
Different colours were doing the same job. Several blues, all serving as the primary action, each chosen in isolation. The drift wasn’t only in how the values looked; it was that no value knew what it was for.
Different colours were doing the same job. Several blues, all serving as the primary action, each chosen in isolation. The drift wasn’t only in how the values looked; it was that no value knew what it was for.
Alongside the audit, the engineers told me what they needed, not through formal interviews, but through the same conversations that had been going on for months. What they asked for during handoff was the research: hex codes, sizes, states. They weren’t asking for a design system. They were asking for names.
Alongside the audit, the engineers told me what they needed, not through formal interviews, but through the same conversations that had been going on for months. What they asked for during handoff was the research: hex codes, sizes, states. They weren’t asking for a design system. They were asking for names.
That reframed the goal. The job wasn’t to reduce the palette. It was to make every value declare its purpose so it could be handed over without me in the room.
That reframed the goal. The job wasn’t to reduce the palette. It was to make every value declare its purpose so it could be handed over without me in the room.
Building the foundation
Building the foundation
Building the foundation
The audit had already decided the shape of the system. If the same value could be a border in one place and a disabled state in another, then organising by appearance was off the table. Every token had to name a job, not a colour.
The audit had already decided the shape of the system. If the same value could be a border in one place and a disabled state in another, then organising by appearance was off the table. Every token had to name a job, not a colour.
Colour: role → category → variant → state
Colour: role → category → variant → state
Four roles cover every surface in the product: surface, content, border, and decorative. Below that, each token narrows to a specific job and the state it applies in.
Four roles cover every surface in the product: surface, content, border, and decorative. Below that, each token narrows to a specific job and the state it applies in.
surface/action/primary/hover
surface/action/primary/hover
surface/static/warning/default
surface/static/warning/default
content/onActionInversed
content/onActionInversed
border/subdued
border/subdued
The split between action and static is the decision I’d defend hardest. Interactive surfaces need a full state ladder: default, hover, active, disabled. Static surfaces need a semantic ladder instead: neutral, info, success, warning, danger. Collapsing those into one tree would have forced hover states onto things that are never hovered, and status meanings onto things that have no status. Keeping them separate meant every token in the file has all four levels populated, and none of them are decorative filler. 71 tokens, no orphans.
The split between action and static is the decision I’d defend hardest. Interactive surfaces need a full state ladder: default, hover, active, disabled. Static surfaces need a semantic ladder instead: neutral, info, success, warning, danger. Collapsing those into one tree would have forced hover states onto things that are never hovered, and status meanings onto things that have no status. Keeping them separate meant every token in the file has all four levels populated, and none of them are decorative filler. 71 tokens, no orphans.


Typography: property × role
Typography: property × role
Rather than bundling type into style objects only, I decomposed it: 13 roles (h1 through h6, three body sizes, two button sizes, two link sizes), each with fontSize, lineHeight, letterSpacing, and paragraphSpacing as separate tokens. Plus four weights and one family.
Rather than bundling type into style objects only, I decomposed it: 13 roles (h1 through h6, three body sizes, two button sizes, two link sizes), each with fontSize, lineHeight, letterSpacing, and paragraphSpacing as separate tokens. Plus four weights and one family.
That’s 57 tokens where 13 styles would have done the same visual job. The reason for the decomposition is that a style is a bundle a human applies; a token is a value a system can reference. The 13 text styles still exist. They’re what I use while designing, but they’re built on the tokens, so the styles and the code read from the same source rather than agreeing by coincidence.
That’s 57 tokens where 13 styles would have done the same visual job. The reason for the decomposition is that a style is a bundle a human applies; a token is a value a system can reference. The 13 text styles still exist. They’re what I use while designing, but they’re built on the tokens, so the styles and the code read from the same source rather than agreeing by coincidence.

Shadows: primitives, not presets
Shadows: primitives, not presets
Seven elevation levels, each broken into five variables: colour, offset-x, offset-y, blur, spread. Thirty-five tokens where seven effect styles would have covered the design work.
Seven elevation levels, each broken into five variables: colour, offset-x, offset-y, blur, spread. Thirty-five tokens where seven effect styles would have covered the design work.
This one felt like over-engineering while I was doing it. It wasn’t. A shadow style in Figma is opaque. It renders correctly and communicates nothing. Decomposed into primitives, elevation becomes a set of numbers anything can read, including something that isn’t a designer.
This one felt like over-engineering while I was doing it. It wasn’t. A shadow style in Figma is opaque. It renders correctly and communicates nothing. Decomposed into primitives, elevation becomes a set of numbers anything can read, including something that isn’t a designer.
That distinction, a value a human applies versus a value a system can address, is what the whole foundation turned on, and it’s what made the next part possible.
That distinction, a value a human applies versus a value a system can address, is what the whole foundation turned on, and it’s what made the next part possible.

Testing whether the system could be read by a machine
Testing whether the system could be read by a machine
Testing whether the system could be read by a machine
The structure was built for engineers. But it raised a question I hadn’t planned for: if every value now declares its own job, does a human still need to be the one translating it?
The structure was built for engineers. But it raised a question I hadn’t planned for: if every value now declares its own job, does a human still need to be the one translating it?
I uploaded the token document into Claude Design and asked it to produce the engineering token file. It generated the CSS variables directly: surface/action/primary/hover became --surface-action-primary-hover, with the resolved value attached. No interpretation, no clarifying questions, no me.
I uploaded the token document into Claude Design and asked it to produce the engineering token file. It generated the CSS variables directly: surface/action/primary/hover became --surface-action-primary-hover, with the resolved value attached. No interpretation, no clarifying questions, no me.
Why it worked
Why it worked
It worked because of the naming, and it would have failed without it. Three properties made the file legible:
It worked because of the naming, and it would have failed without it. Three properties made the file legible:
The names carried meaning. surface/action/primary/hover states its role, category, variant, and state in one string. It maps to a CSS variable with nothing lost. A token named Blue 500 describes a colour and communicates nothing about where it belongs. It would need a human to decide.
The names carried meaning. surface/action/primary/hover states its role, category, variant, and state in one string. It maps to a CSS variable with nothing lost. A token named Blue 500 describes a colour and communicates nothing about where it belongs. It would need a human to decide.
The hierarchy was consistent. Every colour token has the same depth and the same order: role, category, variant, state. Predictable structure means the mapping is mechanical rather than case-by-case.
The hierarchy was consistent. Every colour token has the same depth and the same order: role, category, variant, state. Predictable structure means the mapping is mechanical rather than case-by-case.
The values were decomposed. Shadows split into colour, offset, blur, and spread were translated into individual properties. As a single opaque style, elevation-4 would have needed to be opened, read, and transcribed by hand.
The values were decomposed. Shadows split into colour, offset, blur, and spread were translated into individual properties. As a single opaque style, elevation-4 would have needed to be opened, read, and transcribed by hand.


What this actually proved
What this actually proved
Not that AI can replace design handoff. This was a one-time generation, not a live pipeline: token changes still need a re-run, and there’s no sync between the Figma file and the codebase.
Not that AI can replace design handoff. This was a one-time generation, not a live pipeline: token changes still need a re-run, and there’s no sync between the Figma file and the codebase.
What it proved was narrower and more useful: the structure was correct. A design system that a model can translate without asking questions is a design system that a new engineer can read without asking questions. The model didn’t fix the handoff problem. It demonstrated that the handoff problem was already solved by the naming.
What it proved was narrower and more useful: the structure was correct. A design system that a model can translate without asking questions is a design system that a new engineer can read without asking questions. The model didn’t fix the handoff problem. It demonstrated that the handoff problem was already solved by the naming.
That was the reframe I took from the project. I had been designing for people who would look at the file. Structuring it so that something with no context could still resolve every value turned out to be the same discipline as documentation, enforced by a system that can’t guess.
That was the reframe I took from the project. I had been designing for people who would look at the file. Structuring it so that something with no context could still resolve every value turned out to be the same discipline as documentation, enforced by a system that can’t guess.
Where it landed
Where it landed
Where it landed
The token file is in the codebase and in use. Every new feature is built from it, and the existing UI is being migrated across as screens get touched: a running migration rather than a rewrite, which was the only version of this that could realistically ship alongside product work.
The token file is in the codebase and in use. Every new feature is built from it, and the existing UI is being migrated across as screens get touched: a running migration rather than a rewrite, which was the only version of this that could realistically ship alongside product work.
Four things changed, all of them observed rather than measured. I didn’t instrument this, and I’d rather say so than attach numbers I can’t defend.
Four things changed, all of them observed rather than measured. I didn’t instrument this, and I’d rather say so than attach numbers I can’t defend.
New screens are consistent by default. Consistency stopped being something I maintained through vigilance and became a property of the file. There’s no version of “which grey was that” anymore, because there’s exactly one token for the job.
New screens are consistent by default. Consistency stopped being something I maintained through vigilance and became a property of the file. There’s no version of “which grey was that” anymore, because there’s exactly one token for the job.
Handoff questions dropped. Not to zero, but the category changed. Engineers stopped asking what a value is and started asking what something should do, which is the conversation worth having.
Handoff questions dropped. Not to zero, but the category changed. Engineers stopped asking what a value is and started asking what something should do, which is the conversation worth having.
Designing got faster. The invisible cost of no system was re-deciding settled questions on every screen. That work disappeared.
Designing got faster. The invisible cost of no system was re-deciding settled questions on every screen. That work disappeared.
Less rework on new features. When the design and the build reference the same named value, the class of QA bug where the implementation is slightly wrong mostly stops occurring.
Less rework on new features. When the design and the build reference the same named value, the class of QA bug where the implementation is slightly wrong mostly stops occurring.
The honest summary: I can’t tell you the percentage change in cycle time, because we weren’t measuring it before and I didn’t set up a way to measure it after. What I can tell you is what stopped happening, and that the migration is still going, which is the real test of whether a system holds up.
The honest summary: I can’t tell you the percentage change in cycle time, because we weren’t measuring it before and I didn’t set up a way to measure it after. What I can tell you is what stopped happening, and that the migration is still going, which is the real test of whether a system holds up.

What I’d do differently
What I’d do differently
What I’d do differently
I shipped without a spacing scale. Inconsistent spacing was one of the two problems I identified in the audit, and it’s the one I didn’t solve. Colour, type, and elevation were tractable in 15 days because the values already existed in the file; the work was naming and structuring them. Spacing was different: there was no underlying scale to formalise, only ad hoc values. Building one properly means deciding a base unit and a ramp, then reconciling every existing screen against it, and doing that badly would have been worse than not doing it. So it’s the next layer, not a finished one. Right now, spacing is still the thing engineers have to ask about.
I shipped without a spacing scale. Inconsistent spacing was one of the two problems I identified in the audit, and it’s the one I didn’t solve. Colour, type, and elevation were tractable in 15 days because the values already existed in the file; the work was naming and structuring them. Spacing was different: there was no underlying scale to formalise, only ad hoc values. Building one properly means deciding a base unit and a ramp, then reconciling every existing screen against it, and doing that badly would have been worse than not doing it. So it’s the next layer, not a finished one. Right now, spacing is still the thing engineers have to ask about.
Tokens without components are half a system. What I built makes values consistent. It doesn’t make composition consistent; two engineers can build the same card correctly from the same tokens and still produce different cards. The component layer is where that gets solved, and it’s the natural next phase.
Tokens without components are half a system. What I built makes values consistent. It doesn’t make composition consistent; two engineers can build the same card correctly from the same tokens and still produce different cards. The component layer is where that gets solved, and it’s the natural next phase.
The generation was a one-off. Producing the token file from the design document worked, but nothing keeps them in sync. Change a token in Figma today, and the codebase doesn’t know. A real pipeline needs the export to run on change, not on request. That’s an engineering problem more than a design one, and it’s the piece I’d push for next.
The generation was a one-off. Producing the token file from the design document worked, but nothing keeps them in sync. Change a token in Figma today, and the codebase doesn’t know. A real pipeline needs the export to run on change, not on request. That’s an engineering problem more than a design one, and it’s the piece I’d push for next.
I didn’t measure the before. I knew the handoff was slow. I didn’t count how slow, and by the time I wanted the number, the old state was gone. On the next system-level project, I’d spend an hour establishing a baseline before starting, even something rough, like questions per handoff over two sprints.
I didn’t measure the before. I knew the handoff was slow. I didn’t count how slow, and by the time I wanted the number, the old state was gone. On the next system-level project, I’d spend an hour establishing a baseline before starting, even something rough, like questions per handoff over two sprints.
