The brief was a homepage. The site was load bearing.
Four companies had worked on this WordPress site over the years, and each one left a layer. Theme components at the bottom, a page builder on top, and now a new homepage to build. The same install also runs the B2B entry points, the appointments and the coupons, which meant a design change and an outage were the same event.
Every new page made the next one harder
Four companies had maintained this site over the years. Each one left a layer.
At the bottom sits a theme with its own components, written in PHP. Newer sections sit on top of it, built in a page builder. The two do not share a styling model or a breakpoint model, so updating one could break the other, and it often did. Over time people got careful. Nobody wanted to touch anything, because nobody could predict what a change would hit, so new work got bolted on rather than built in.
Behind the marketing pages sit the parts nobody demos: B2B entry points, an appointment system, promotions, coupon management. These are real tools with real users, and they run on the same install as the homepage. A bad line of CSS in the global header does not simply make a page look wrong. It reaches the booking form and the coupon tool at the same moment, while people are using them.
Most of what follows comes out of that one fact.
An opt-in system, not a redesign
The business requirements were real and had to be met. But meeting them on their own would have fixed one quarter and made the following year worse, because the new homepage would have become a fifth layer on a site that already had four.
A rewrite was never on the table. You cannot freeze a site that is taking bookings. So the homepage became the first application of something larger: a shared set of components and values that the rest of the site could adopt gradually, at whatever pace the client could afford.
That produced an unusual first requirement. The system had to be capable of doing nothing. A design system that arrives and restyles things is useless here, because “things” includes the appointment form. So the tokens publish across the whole site, which is safe because they are only declarations and nothing changes until a page picks them up. Every actual style sits behind a class that only the new page carries. The site opts in one page at a time, and it can stop at any point without leaving anything broken.
Values, then components, then the page
The design work was mostly a search for the parts of the homepage that were not really about the homepage: the shapes that would come back on a product page, a news archive, a contact page. That gave us 25 components. Around a third of them appear only once, but each was defined as though it would be reused, because eventually it will be.
The build order matters, because each layer is only correct if the one underneath it is. Tokens hold every value. Text styles and components reference those tokens rather than containing numbers of their own. Layouts are templates you instance and detach. The page is an output of all of it.
Two decisions turned out to matter more than they looked. We kept two grey ramps rather than one, a cool grey for text and a warm grey for surfaces. Merging them would have been simpler, and would have made the site quietly wrong in a way that is hard to point at.
We also separated modes from variants. A gutter that goes from 60 to 20 on mobile is a mode: one token, two values. A menu that becomes a burger is a variant, because the structure genuinely changed. Confusing the two leads to duplicating components in order to store numbers, which is a common way for a library to grow without getting better. In the end, eight values change between breakpoints and nine components rearrange. Nothing else responds at all.
Header and footer rollout, reordered around risk
My plan was to start with the header and footer. They appear on every page, so you get the most reuse soonest. It seemed obvious.
Then the engineer mentioned, more or less in passing, that the header and footer are not built in the page builder at all. They are theme templates. He was answering a different question and did not appear to think it was significant.
It changed the whole plan. Replacing a theme template does not affect the homepage, it affects every page at once, including the appointment system and the coupon tool. The reuse that makes the header attractive to build first is the same property that makes a mistake in it expensive. Building it first would have spent the entire risk budget on the least tested work in the
project.
So we inverted the order. The homepage gets its own template, which renders a new header and footer while the rest of the site keeps the old ones. Everything stays scoped and reversible. Only after the whole page has been built and tested does the header and footer swap go site wide, and by that point it is a change that has already been proven in place.
One was visible.
A routine check turned up a frame collapsed to a sliver, quietly cutting off its own content. The cause was a quirk in the tool: resizing a frame silently locks its height, even immediately after you have told it not to. Rather than fix the one we could see, we swept for the whole class of bug.
Fifty four of them were invisible until somebody went looking.
The check found the system wrong, not the build
Once the library existed we rebuilt the original design out of it, measured both, and compared. The point was to test the system rather than the page.
It failed. Three type sizes were wrong on desktop, and had been wrong for six versions.
| Library | The design | ||
|---|---|---|---|
| Section headings | 28px | 46px | half size, on every section |
| Stat numbers | 28px | 60px | a bug we had introduced |
| Hero headline | 48px | 60px | one step too small |
The section headings were the instructive case. The component itself was built correctly. The page was simply calling it at the wrong size, and nothing in the file could have told us that. A component can be flawless and still be used wrong.
I got one of these wrong twice. I had measured the stat numbers at 48px and written that into three documents. The client said they looked too small and that the original was 60. He was right. I had misread a grouping in my own measurements, and then treated my own output as evidence. Measure the source, not your notes.
The same check saved something we were about to flatten. On mobile, the stat numbers are deliberately larger than the headline. It looks like an error and it is not, because the numbers are the argument the page is making. A tidier type scale would have quietly removed it.
Splitting the job between Figma and code
Teams argue about this endlessly. Is Figma the source of truth, or the code? Both answers tend to fail the same way, because both files end up holding the same information and slowly disagreeing about it.
We split the responsibility instead. tokens.json owns every value: colour, size, space, radius, shadow, breakpoints. The Figma library owns the components: what exists, what variants it has, what slots it exposes. Values flow in one direction, from code into Figma.
This does not make drift impossible. A token reference can break, a component can be edited without rebinding, an engineer can implement something the library never described. What it removes is the most common failure, where two files each define the same value on their own and diverge over time. The two systems no longer define the same values independently, and the failures that remain are the kind you can go looking for, which is what the routine in chapter 10 is for.
The rules you cannot see by looking at Figma
Every decision went into a file, and every file went into git. Decisions that stay in a chat thread get forgotten within about a week. Six months from now nobody asks what a value is. They ask why it is that value, and whether they are allowed to change it.
The most useful document is the one the engineer reads first, before the component list. Each of these rules has already caused a bug on this project, and none of them are visible in the design file.
- Capitals are CSS, never typed.The site runs in three languages. Typed capitals can interfere with translation matching and assistive pronunciation, and they remove the accents from Greek capital letters, which is a spelling error rather than a style choice.
- No hardcoded values.If you are typing a colour, something has gone wrong upstream.
- Bands run edge to edge, content stays in a box.Adding side padding to a band turns it back into a card.
- Shadows need room.A 70px blur needs roughly 100px of space. No hidden overflow anywhere near one.
- Two colour numerals are two elements.The red plus in "10.000+" is a span, never an image and never the whole string.
- The configurator is hidden on mobile, not removed.It stays in the page source and a media query hides it.
Two handover documents, one for a person and one for a model
The engineer gets the brief above. His AI assistant gets a second one, written for a model rather than a person, which turns out to be a different document. Less “here is how it works”, more “here is the confident wrong thing you are about to do”. Do not remap the global colours, because it restyles every old page the moment you save. Do not put styles in the page builder, because they end up in the database instead of git. The library says this number is 48, but it is 60, so trust the tokens.
Whether to buy the paid page builder
The site runs an old, free version of its page builder, and the obvious move was to buy the paid tier. We had assumed we would. Two things changed the answer.
The paid tier’s headline feature lets you write CSS on any element from inside the editor, and stores it in the database. Not in git, not searchable, and not reviewable in any practical way. For a design system that is a slow leak rather than a sudden failure, and it is hard to detect until the styling has already fragmented.
The second reason was less philosophical. A current licence requires a current version of the builder, and this site was three years behind. Buying the licence would have forced an upgrade the client had deliberately deferred, and put a risky migration onto the critical path of a homepage build.
The real cost of not buying it came to two custom shortcodes.
The routine
Design systems rarely decay because people are careless. They decay because nothing obliges anyone to look. So the last thing we shipped was a loop, run after every completed change.
It is written into three places, the repo, the Figma file and the AI agent’s instructions, so that it runs whether or not anyone remembers to. The list will keep growing.
What exists now
25
Components
102
Tokens
0
Hardcoded values
2
Sources of truth
The homepage can be built without touching a single old page, and without the bookings or the coupons noticing that anything happened. The next page will be cheaper than this one, and the migration can stop at any point without leaving the site half converted. The handover works for an engineer and for the AI agent sitting next to him, and the routine should keep it honest after I have moved on.
What I would do differently
Check the library against the design much earlier. Three type sizes were wrong for six versions because we kept checking the build against the library, and never the library against the design. That check belonged in version one, and it cost almost nothing to run.
Ask what the header is made of on day one. The entire build order depended on an implementation detail that nobody thought to mention, because from the engineering side it did not look like a design question. It turned out to be the most consequential one on the project.





