← Work/ACE Design System

Building KTO's design system to scale across brands and themes

How I rebuilt KTO's design system, ACE, for multiple brands and themes on web and Flutter.

Role

Design system lead and owner

Team

DesignOps, web technical owner, Flutter technical owner

Stack

Tokens Studio, Style Dictionary, GitHub, Storybook

55
Components in production, rebuilt from 43 design-only components
6 squads, 33 engineers
Using it on web and Flutter

Face verification

Before you continue, we need to verify your identity. It only takes a moment.

Back
Start

A three-brand license turned a component library into an architecture problem

KTO was a licensed betting platform in Brazil. The federal license cost R$30 million, lasted five years and let each operator run up to three commercial brands. KTO ran one, and new brands were on the plan for 2027. Each of them would need its own look on the same product, built by the same squads.

The system KTO had was made for one brand and one theme. Adding a brand to it would have meant restyling every component by hand, on web and on Flutter, for every brand. The question stopped being how screens should look and became how one system could produce brands that looked different without a rebuild each time. That made it an architecture problem before it was a design problem.

The old system had 43 components, and none of them reached code

What KTO called a design system was a Figma library with 43 components. None of them existed in code. The library had no inventory and no criteria for what deserved to be a component, and most of a design system's value sits on the development side, which was empty.

I owned the design side of the new system alone, with one technical owner for web and one for Flutter. The fastest path would have been to hand the existing library to engineering and code it as it was. I ruled it out early: coding 43 components without criteria would have turned their inconsistencies into code, where they cost far more to fix.

Every inherited component had to earn its place

I reviewed every one of the 43 components. Twenty-nine overlapped so much that they merged into 13, twelve went away, and two survived with a one-to-one rework. I then designed 40 new components for what the product actually needed, which brought ACE to 55 components in design and code.

43 → 15
inherited components that survived the audit: 29 merged into 13, 2 reworked, 12 removed

The same discipline applied to tokens. Two tokens that happened to share a value stayed separate when they meant different things, because a shared value today says nothing about tomorrow's brand. I recorded that rule as a documented decision so nobody would merge them later to save a line.

43 inherited components,
none of them in code
Merged29 → 13
Reworked2 kept, redone
Removed12 → 0
55 15 kept + 40 new,
all in design and code

Components never know which brand they're on

Before any layer, the themes had to settle. The product mixed light and dark screens, and in a token system one inconsistency cascades through every component that references it. I proposed dark as the default: it matched the brand, most of the product already used it, and it was the market standard in betting. The Head of Design made the call, and I set up Tokens Studio for light and dark switching from the start so a light theme could arrive later without a rebuild.

The architecture had four token layers. Global held raw values with no meaning, brand held each brand's overrides, theme handled light and dark, and component tokens sat on top. Components only referenced component tokens, so a component never knew which brand it was on. A new brand meant a new brand file, and every component stayed untouched.

Typography went above the components for the same reason. With type inside each component, every brand would have needed overrides at component level, and neutrality would have broken at the first new font. Above them, one change reached every component at once.

Globalraw values
green.500 = #00DD70   blue.600 = #0F74D2
neutral.900 = #1E1E1E   neutral.100 = #EEEEEE
Brand brand/kto.jsonbrand/brand-b.json
primary → green.500
font → sans, heavy italic · radius → 12
primary → blue.600
font → script + serif · radius → pill
Theme theme/dark.jsontheme/light.json
surface → neutral.900
text → white
surface → neutral.100
text → neutral.900
Componentcomponent/modal.json · untouched
fill → surface
button.primary → brand.primary
KTO · dark

Web and Flutter only shared values after we agreed on units

Tokens lived in Tokens Studio and exported as JSON to GitHub. Every push to main triggered an automatic build that turned the JSON into platform code, including a Flutter package. The JSON was the source of truth, ahead of Figma and Storybook, which kept design in control of what reached the front end. A developer added each component to Storybook, and a component was only done once I approved it there.

The first conflict was units. Web and Flutter read values differently, so one JSON value could not serve both as it was. I proposed conventions both platforms could accept: percentages as decimals, durations in milliseconds and easing as platform keywords. Both technical owners adopted them in production.

Tokens Studio

Designers edit tokens in Figma

global
brand/kto
theme/dark
theme/light
component/card

JSON in GitHub

The source of truth, ahead of Figma and Storybook

global.json
brand/kto.json
theme/dark.json · light.json
component/card.json
pushed to main

Build

Runs on every push to main

✓Read the JSON
✓Resolve references
✓Generate platform code
✓Publish the packages

Web

web package

Flutter

Flutter package

Storybook

Done only when design approves

Design approval✓ Approved

Teams shipped components before we said they were ready

ACE shipped while I was still building it. The Head of Product set a deadline for Q4 2026, opened a dedicated channel, gave the work executive visibility and made adoption a formal mandate. The Head of Design asked for the Native Casino app to follow ACE as well. Migration followed the product roadmap: any revamp, redesign or new feature moved fully to ACE components and tokens, while small fixes stayed as they were. The Register Flow, My Account and the Responsible Gambling Limits Hub went first.

That momentum had a cost I underestimated. Squads put components in production before I had said how stable each one was, and every new use turned into a manual conversation about whether it was ready. A maturity model with draft, beta and stable labels would have answered that question once for everyone. I never implemented it, and it is the first thing I would set up in my next system.

Limits Hub screen

A corrupted token file proved the overwrite risk I had flagged

As more designers joined the pipeline, one push to the token repository could erase another. In early September I flagged the risk to the web technical owner and laid out three options: a branch per designer, one JSON file per component, or both. He suggested a branch per task, which matched the team's standard flow, and I decided to adopt it. I also proposed one file per component, because branches alone left the shared file, the root of the risk, in place.

In mid-September a merge between two component branches corrupted the shared token file. Git compares lines, not JSON structure, and rebuilding the file took hours. The failure confirmed the diagnosis, and from then on every new component lived in its own file.

Scaling the system meant teaching designers, not building more myself

By then design throughput had become a bottleneck for engineering. I founded KTO's DesignOps function without formal authority, since the Lead Designer managed the designers. I grew the component pipeline from just me to three designers, with weekly sprints and regular critique, and trained two peers in Tokens Studio and in how a component travels from Figma to code.

Whenever I could, I taught instead of doing. When a mid-level designer needed a 500px max height, I walked him through our rem scale instead of creating the token for him, and we landed on 32rem together. He went on to build and document components and open the pull requests himself, with me reviewing them.

I also wrote rules where the system had none. The notification components (toast, banner and info) shipped without usage rules, so I proposed the principle I had written for the Limits Hub, and the team adopted it.

The team already held weekly critiques. On large projects, I also asked for the file and left questions instead of changes, so newer designers walked into stakeholder reviews with answers ready. Because any component heading into ACE had to follow our tokens, I created the handoff standard the whole design team used. The two designers who joined after me also adopted the workshop format and hypothesis model from my projects.

55 components in code, and the adoption metric I never got to measure

ACE reached 55 components in design and code, used by six product squads and 33 engineers on web and Flutter. It carried 19 documented decisions, 27 component pages and a register of 18 known issues. An audit flow I built with Claude helped surface those issues by cross-checking Figma, Storybook, tokens and documentation.

The one measured result is narrow. On my own handoffs with one developer, clarification tickets fell 48% before and after, as reported by the Payments PM, and I don't extend that number to the whole system. Adoption in code was the next thing I would have measured.

The official v1.0 was planned for November 2026, when every component we had mapped would be in production. KTO closed in October 2026, a month short of it. No second brand ever ran on ACE, but the system was ready for one: adding it meant writing a new brand file.

Next case

Responsible Gambling Limits Hub

One of the first surfaces built on ACE, and where its notification rule came from.

Hiring?I'm open to remote Staff and Senior IC product design roles.