
Every capture in this case comes from Norte running in a browser, with no mockup or Figma frame.
In 30 seconds
- The app and mobile site had been running for years on interface decisions nobody had written down. The same decision existed in several versions, and design and code had separate sources of truth.
- I consolidated the foundations and built Norte: one spec per component that becomes the docs, the web and app components and a package AI reads and reviews code with.
- All 11 foundations are in use. Of the 33 components specified, 12 are already in Norte, and today's app has a documented mapping to the new system.
- Bemol Digital
- Senior Product Designer
- Design system · Foundations and components
- Mobile app and web
- In production · internal hub · component library in progress
The system came after the product
The product had been shipping for years, across an app and a mobile site, with its interface decisions already made. They just weren't written down, and they hadn't come from one place. A design system that starts from zero picks how things will be; this one had to find out how they already were.
So the first half of the work was archaeology rather than design: finding where a decision had already been taken, deciding whether it was worth keeping, and giving it a single home.
That home is called Norte, Portuguese for north, in both senses of the word: the compass one, the point everyone checks before deciding where to go, and the map one, Brazil's North, where Manaus is and where the product comes from.
The size of the system
Scope figures, measured on 18 September 2026.
- 33
- components specified
- 330
- documented variations
- 12
- already in Norte, with Design's guidelines
Nobody had to decide what a button should look like. The hard part was finding the eleven places that had already decided, and picking one.
Three problems, none of them cosmetic
The same decision existed in several versions
Spacing, radii and text sizes had drifted apart across screens built by different people at different times. None of the versions was wrong on its own. Together they meant no screen could be assembled without re-deciding things that should have been settled.
Documentation that nobody opens is not documentation
A system only holds if a designer can answer a question faster by looking it up than by asking someone. That is a usability requirement on the documentation itself, and it sets the format: short answers, one place, findable under pressure.
Design and code had separate sources of truth
A component library that agrees with the design file only at the moment it is written will disagree by the next sprint. The system had to be defined so that both sides read from the same values instead of copying them.
Figma decides, everything else reads
Where the names just below this come from.
None of the places the system shows up decides anything. They all read, and Figma decides. Every colour, radius and spacing starts there and gets transcribed into a token file. There are 367 of them, in one file, in the open format the W3C maintains for design tokens.
Change the ramp step, or the radius, and watch what moves with it. None of these surfaces stores a colour or a radius: they all read the same variable, which is how one decision lands in four places at once.
One command turns that file into four things: the CSS custom properties for the web documentation, the Dart token class for the app, the constants the coded components consume, and a version for AI tooling, without anything copied by hand.
The outputs differ on purpose. The code ones keep the whole chain: a component points at a semantic token, which points at a primitive. The agent's copy ships the resolved value, because whoever reads it wants the hex and has no use for the lineage.
The price is in the first sentence. The transcription is manual: someone opens Figma, sees what changed and writes it into the file. As long as that's true, the system is exactly as current as the last time a person looked.
The state comes from the matrix
The moment a component's states are drawn by hand, they start diverging: one screen has a focus ring, another forgot it, a third invented a different one.
| type | enabled | pressed | loading | disabled |
|---|---|---|---|---|
| primary | ||||
| secondary | ||||
| tertiary |
Both rules the text just below explains are drawn here, and you can check them without reading: the loading and pressed cells carry the same colour, and the disabled secondary is the only one still white. Below, the three sizes: the small one is 32 pixels of visual inside a 44-pixel touch target.
Defining states as a matrix instead, every type against every state, turns a design question into a completeness check. If a cell is empty, that's a gap in the system, visible before it becomes a bug in production.
And the matrix settles things nobody would settle looking at one button. Two of them are in the grid above: loading borrows the pressed colours, because the button is still live while it waits; and the disabled secondary stays white, since what fades there is the border and the label.
Accessibility is a system property
Focus visibility, contrast and error handling are the first things to disappear when each screen is built separately, because each one is easy to skip and nobody notices until an audit.
Contact use only.
Disabled: you can't reach it by use.
Type, clear it, leave out the @. The border swaps token and the message shows up in the 16 pixels already reserved below the field. The whole block occupies the same 72 pixels in every state, and nothing pushes anything.
Put in the system, they stop being a per-screen decision. Two of them are written into the spec, out of reach of whoever assembles the screen: an error is a border plus an icon plus text, never colour alone, because anyone who can't tell red from grey needs a second cue; and the height of the message is reserved before the message exists, or everything below it jumps the moment validation appears.
Norte: four doors, one house
One address, and each role walks in through its own door.
A system scattered across different tools charges every person the same toll: working out where to look. Norte answers with a single address. The first page asks what you came to do rather than which tool you want to open, and the four doors are design, build for app, build for web, generate with AI.
The entrance asks who you are before showing any tool. The web door is the only one with a work-in-progress sign, because its Figma library isn't closed yet.
Behind the doors, the same source. Every component has a specification written in Markdown, and that file produces the page a designer consults, the reference React for the web, the Flutter widget for the app and the file an AI tool loads. Since there's only one file, nobody edits the four places separately.
It started as three showcases, each at its own address: a landing page, Storybook and Widgetbook. They became one house. Storybook is the frame, Widgetbook runs inside it, and each component lives in one place, with the spec and the app and web code side by side. The sidebar splits app and web because the web library in Figma isn't closed yet, and anyone coming in needs to know that.
Web and app aren't two copies
The question that decides whether a cross-platform design system works is a mundane one: when the button changes, how many places have to be edited? If the answer is two, the system has already lost. The two will drift, and the drift surfaces months later, on a screen nobody is watching.
On the left, the specification the team reads, in the same four guideline tabs Design delivers in Figma. On the right, one tab over, the same button as a Flutter widget. It's the code the app consumes, running at the same address.
Here the specification is one, and each environment reads its own part of it. Only the language the component is written in changes between them; the decision stays the same.
Today's app lives here too
A mapping between what the app uses today and what the new system defines.
The app already had a design system in code, written before several foundations were settled in Figma and untouched since. Ignoring it would mean pretending the product starts now. It became a root of its own in Norte, Production (app today), with a mapping per foundation: the token the app uses, the value it has today and the new token that takes its place.
At the top, where each foundation hurts. Below, the five traps side by side: the Dart name the app uses today, its value, the new token and the new value.
The table exists because of one specific trap. Most of the migration is renaming, but five tokens keep the same name with a different value, and those are exactly the ones a find-and-replace sails past. The large radius was 24px and became 20; the medium stroke was 2px and became 1.5. None of them breaks the build, but all of them change the pixel on screen without anyone deciding to.
The page doesn't say which value is right. Figma still decides; the mapping only shows, swap by swap, whether it's direct or needs a designer's eye before it ships.
The fourth door is for the machine
If the team generates code with AI, the system has to live where the AI reads.
Designers and developers stopped being the only readers of a design system. When someone asks an agent for a new screen, the agent is the one choosing spacing and colour. Without the system nearby it invents a plausible value, which is the worst kind of error: it survives a quick review and matches nothing.
On the left, the index: each component with the state it's in and what it depends on, which keeps an agent from building a component on top of one that isn't ready yet. On the right, the review skill's page, with its two modes and the file to download.
So the same source generates a package for tooling: an index of what exists and in what state, the specification for each component and the token values, in three files to download and paste into the tool, one for foundations, one for app components and one for web. A rules document goes with it, and the rule that matters most is the one that says to ask: if the token doesn't exist, don't approximate.
Reading wasn't enough. Components built at the last minute, outside the flow, arrived without following the system and without docs. So the door also reviews: a skill checks the code against the system and writes the draft specification. What's a rule became a script, the same every time: a raw value where a token belongs, a primitive where the semantic one belongs, a token that doesn't exist. What's judgement stays with the AI: invented variations, missing states, accessibility.
The specification goes in as a draft, with a list of what the code decided without Figma, for Design to review and promote. The skill never invents a token. It ended up a skill rather than an agent because a skill is plain text, and the same one runs in Claude Code, Claude.ai and Cursor. Its first pass found three errors in code that already existed, and none in the reference components.
What didn't ship
A component for every pattern in the product
Some patterns appear on one screen and will never repeat. Systematising them adds maintenance cost and buys nothing. A system that covers every case ends up too rigid to change.
The way back, from code to Figma
The outbound path exists: one token file becomes all four outputs with a single command. The return path doesn't. Changing a colour in Figma doesn't move the file on its own, and a person still transcribes the value. The drift between the two sides is recorded and reviewed rather than pretended away, but it remains the most expensive debt the system carries. And the next step isn't automating that transcription: it's not needing it. That isn't decided, and it isn't only my call, but it's the day I've been building for. With the values in a versioned file and the spec in plain text, changing who decides becomes a change of origin rather than a restart.
A focus ring that passes contrast
The system's ring is the brand blue at 32% opacity. Measured against white it lands around 1.9:1, and WCAG 2.2 asks for 3:1 on a focus indicator. The button matrix on this page shows the ring as it is. Fixing it only here would make the case look good and leave the product unchanged, and it's one of the first things I'd take into the next pass on the foundations.
Retrofitting every legacy screen at once
Rewriting a shipping product wholesale to satisfy the system is how systems get abandoned. New work uses it; old work migrates when it's touched for another reason, component by component, with the Production mapping saying what changes the pixel.
A package only tested from the inside
The app package had 119 passing tests and a running catalogue, and nobody had installed it from the outside. The first time a minimal app did, the icons came out as empty boxes within minutes: Flutter only honours the Material fonts flag in the app's project, not the package's. The plan said the opposite. The package now ships with that example app, and this was the first warning taken to the app team.
Who did what
- Consolidating scattered decisions into a single set of foundations
- Structure and format of the documentation, so it answers questions faster than asking a person does
- Component states defined as a matrix rather than screen by screen
- The rules that come before components: grid, vertical rhythm, hierarchy
- Norte and the path out of it: one specification per component as the source, generating the documentation, the app showcase and the package AI tools read, built by me in pair with a coding agent
- The Production root: the mapping between today's app tokens and the new system's, foundation by foundation
- The skill that reviews a component against the system and drafts its specification for Design
- Component library, built with the design team, still in progress
- Implementation and API of the coded components, with engineering
- Review of interface copy, with the writing side of the team
Where it stands
The 11 foundations and the screen-building rules are settled and in use, and since August 2026 they have an address: Norte serves all four doors from the same source. The component library is the part still under construction. Of the 33 specified, 12 are already in Norte with Design's guidelines transcribed: nine run as code in the app and on the web, and three mark out system space and have no code. Counted by variation, that's 93 of 330, about 28%.
Norte is internal, and it will stay that way: it exists for the team building the product. That's why this case shows screenshots instead of a link, and why the demos on this page were rebuilt from the real specification, which is as close as you can get to opening the tool.
Reflections
What's left when the files get old.
Every artefact here (tokens, matrices, rules) is a way of storing an agreement so it doesn't have to be reached again. The agreement is the hard part; the file is where it sits.
The measure I'd use has nothing to do with component count. It is how many questions stopped being asked, and how many screens now get built without anyone re-deciding what was already decided.