Generated code, assets and tokens: fix the translation¶
Exercise generated-code-assets-tokens-annex.pdf · PDF
Open a page in your browser, window maximised. You get a 390-pixel strip in the top-left corner, three broken images, and the rest of the screen empty. Drag the window narrower: nothing reacts. Press Tab a few times. Where is the focus? Nowhere.
A machine made that page from a perfectly good Figma frame called Notes · 390, for an app called NoteBox. Every colour is right. Every font size is right. And it is useless. This page teaches you to read what a machine wrote, name what is wrong with it, and fix it. You will need those eyes every time an exporter, a plugin or a site builder hands you code.
What you will learn
- Recognise code generated from a rigid frame, and name its typical problems
- Define a design token, and write Figma variables as CSS custom properties
- Replace absolute positions with flexbox, using words you already know: Fill is
flex: 1 - Replace
divsoup with the right HTML tags, and say what each one gives the user - Export an icon as SVG and a logo as PNG at 1x and 2x
- The trap: why you keep the generated values and replace the structure
A machine translation¶
Keep one picture in your head for the whole page: translation. This is what a machine gives when it translates a rigid Figma frame word by word: every layer becomes a div, every position a pair of pixels. Every word is right. Is it what the designer meant?
Here is one row of the note list, as the machine wrote it (the CSS is shortened):
<div class="instance-2">
<div class="rectangle-2"></div>
<div class="text-5">Maths homework</div>
<div class="text-6">Edited yesterday</div>
</div>
.notes-390 { position: relative; width: 390px; height: 844px; background: #FFFFFF; }
.instance-2 { position: absolute; left: 0px; top: 69px; width: 358px; height: 69px;
border-bottom: 1px solid #D4D4D8; }
.text-5 { position: absolute; left: 48px; top: 12px; color: #111111; font-size: 16px; }
position: absolute pins an element at exact coordinates, whatever happens around it. .instance-2 is 69 pixels from the top because the row before it is 69 pixels tall. What if that row's title wraps to two lines?
Not every machine goes this far. Figma's Dev Mode gives only snippets. Exporter plugins and site builders give whole pages like this one.
Eight problems, named¶
To spot them, compare the page with the original frame. Narrow the window. Press Tab. Read the tag names. Each problem has a name you can reuse every time you inspect generated code.
| # | Problem | Where | What the user suffers |
|---|---|---|---|
| 1 | Frozen size | width: 390px; height: 844px on the screen |
A strip in a corner; on a small phone, a cut page |
| 2 | Absolute everything | position: absolute; left; top on every rule |
A longer title overlaps its neighbour. Nothing can grow |
| 3 | Magic numbers | #111111 written 13 times, 16px everywhere |
Nothing today. The day the brand colour changes: 13 edits, one forgotten |
| 4 | Div soup | No header, main, h1, ul, li |
A screen reader announces nothing: no title, no list of 4 items |
| 5 | Fake buttons | div with onclick |
No keyboard focus, no Enter key, no "button" for a screen reader |
| 6 | Fake checkboxes | An empty div painted as a square |
They cannot be ticked. The state is a painted square |
| 7 | Images with no alt (and no file) |
img with only a src |
Broken icons now; unnamed controls later |
| 8 | Missing head lines | No lang, no viewport meta, a font with no fallback |
On a phone the page is shown zoomed out, as a tiny desktop page |
A magic number is a value typed directly into the code, with nothing to say what it means or where else it is used. The viewport meta is the line that tells a phone to use its real width instead of pretending to be a desktop.
Careful: do not throw it all away
"The generated code is rubbish, I will write it from zero." Count what is right in it: every colour, every font size, every padding. The vocabulary is perfect. You keep the values and replace the structure.
It looks the same as the design, so it is right?
At one width, with a mouse, and with good eyes. Resize it, press Tab, turn the images off. And absolute positioning is not forbidden: it is the right tool for a layer that floats over the rest, like a badge over a corner. It is the wrong tool for a whole page.
Design tokens: one name for one decision¶
Start with the magic numbers, because the fix is the most satisfying.
A design token is a design decision with a name, stored in one place, that both the design file and the code use.
The name was coined at Salesforce, by Jina Anne and Jon Levine, for their design system. In Figma a token is a variable. In CSS it is a custom property: a value you name yourself, with two hyphens in front, and read back with var().
| In Figma | Defined once, in tokens.css |
Used everywhere |
|---|---|---|
color/text = #111111 |
--color-text: #111111; |
color: var(--color-text); |
space/4 = 16 |
--space-4: 16px; |
padding: var(--space-4); |
radius/md = 8 |
--radius-md: 8px; |
border-radius: var(--radius-md); |
The translation rule is short: a slash becomes a hyphen, two hyphens go in front, numbers get px. The definitions live in a :root block, so the whole page can use them:
:root {
--color-bg: #FFFFFF;
--color-text: #111111;
--color-muted: #666666;
--color-surface: #F1F5F9;
--color-border: #D4D4D8;
--space-3: 12px;
--space-4: 16px;
--radius-md: 8px;
}
Link tokens.css in the HTML before style.css. Try changing --color-text to red at this point. Nothing changes. Why not? Because nobody uses it yet. A token does nothing until a rule says var(--color-text).
Careful: a token is not a CSS variable
A CSS variable is one way to store a token. The token is the decision: "body text is this black". It also lives in your Figma file. Same decision, more than one file. And tokens are not only for colours: spacing and radius are decisions too. The file behind this page has 7 colours and 8 numbers.
The idea has grown into a standard: the Design Tokens Community Group published the first stable version of its specification, 2025.10, in October 2025.
Fix it, in order¶
Fix in this order, because each step makes the next one easier. The first three matter most.
| # | Fix | You are done when... |
|---|---|---|
| 1 | Tokens in. Replace every hex value and every spacing value with var(--...) |
Searching for # in style.css finds nothing |
| 2 | Absolute out. The screen is a flex column, the header a row with space between, the list takes the free height, the two buttons share the bar | Searching for absolute finds nothing, and the page follows the window |
| 3 | Real tags. header, h1, main, ul and li, button |
Tab reaches the buttons |
| 4 | Real checkboxes. input type="checkbox" with a label |
Clicking a title ticks its box |
| 5 | States. :hover on rows and buttons, :focus-visible on buttons |
You can see where the keyboard is |
| 6 | Head. lang="en", the viewport meta, a font fallback |
The phone view in DevTools shows the page at real size |
:hover styles an element when the mouse is over it. :focus-visible styles it when the keyboard has reached it. DevTools is the set of developer tools inside your browser: press F12 to open it.
From stacks to CSS¶
You already know flexbox. You just learned it with other names, in Framer and in Figma, where Hug is Fit Content:
| Framer | CSS |
|---|---|
| Stack, vertical, gap 16 | display: flex; flex-direction: column; gap: var(--space-4); |
| Distribute: space between | justify-content: space-between; |
| Fill, along the direction | flex: 1; |
| Fit Content, along the direction | Write nothing |
| Height Viewport 100 | min-height: 100dvh; (at least the height of the window) |
| Max width 720 | max-width: 720px; |
Here is the row from before, translated by a person who knows what the designer meant:
<li class="note-row">
<input type="checkbox" id="n2">
<label for="n2">
<span class="row-title">Maths homework</span>
<span class="row-meta">Edited yesterday</span>
</label>
</li>
.note-row { display: flex; align-items: center; gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border); }
.note-row label { display: flex; flex-direction: column; flex: 1; }
.row-title { color: var(--color-text); font-size: 16px; }
.row-meta { color: var(--color-muted); font-size: 13px; }
No coordinates, no fixed width. A longer title now pushes the row taller instead of crashing into its neighbour. And the classes say what they style: what is text-7? Nobody knows. What is row-title? Renaming is part of a good translation.
What the real tags give the user¶
| Replace | With | What the user gets |
|---|---|---|
div.text-1 |
h1 |
A title a screen reader can announce and jump to |
A column of div |
ul and li |
"List, 4 items" |
div with onclick |
button |
Keyboard focus, the Enter key, and the role "button" |
| A painted square | input type="checkbox" + label |
It can be ticked, and clicking the title ticks it too |
This is what you learned in accessibility: use the real control. A div with onclick is a button for a mouse. It is not a button for a keyboard, a screen reader or a form.
Expect these four bumps on the way:
| What you see | Why | What to do |
|---|---|---|
You delete position: absolute and the page collapses into a pile |
Good: now nothing is positioned | Add display: flex to the parents, from the top down, one container at a time |
Rows are stuck at width: 358px |
A leftover from the drawing | Ask yourself: is a row Fixed or Fill? Delete the width |
A button looks grey |
The browser has opinions | Set border, background and font in the rule: your tokens override them |
| A black icon vanishes on a black button | The exported SVG is black | filter: invert(1) is the one-line fix; inline SVG is the proper one, for later |
Export the assets¶
Three images are broken because the files are missing. An asset is a file your page uses but does not contain, such as an icon or a logo. They come from the design file, and you choose the format by what the image is:
| Asset | Format | Why |
|---|---|---|
| Icons | SVG | A drawing made of lines, described as text: sharp at any size, a few hundred bytes, colour editable |
| A logo for app icons and favicons | PNG at 1x and 2x | For the places that want pixels. 2x is the same image with twice the pixels per side, for high-density screens |
| Photos | JPG or WebP | Millions of colours and no sharp edges |
In Figma, select the layer. At the bottom of the right panel, go to Export, press +, choose the format, and export. For the logo, press + twice: PNG at 1x and PNG at 2x. Figma adds the suffix @2x for you.
The layer name becomes the path: the slash turns into a folder. So layers named icon/search and logo/notebox give you:
Reload the page and the broken images heal by themselves, because the generated HTML already pointed at those paths. That is why a clean layer name in Figma is worth the five seconds.
Careful: 2x is not twice as big
A 2x PNG shows at the same size on screen, with twice the pixels per side: four times the pixels in total. And SVG is not "for the web" with PNG "for apps": SVG is for drawings, PNG is for pixels. Both work in both.
Which images need words?¶
An icon next to the words "New note" adds nothing, so alt="" tells the screen reader to skip it. The search icon stands alone, so it needs a name, and it should be a real button:
<button type="button" aria-label="Search notes">
<img src="assets/icon/search.svg" alt="" width="24" height="24">
</button>
<button type="button">
<img src="assets/icon/plus.svg" alt="" width="20" height="20"> New note
</button>
aria-label gives an element a name for screen readers when it has no visible text. As you learned with WCAG, text alternatives are for content, not for decoration.
Figma can export assets, and its variables too. What it cannot know is that a rectangle is a checkbox. That part is yours.
Key takeaways¶
- A machine translates a rigid frame word by word. You keep the values and replace the structure.
- The eight problems: frozen size, absolute everything, magic numbers, div soup, fake buttons, fake checkboxes, images with no
alt, missing head lines. - A design token is a named decision. In Figma it is a variable, in CSS a custom property:
space/4becomes--space-4: 16px;. - Stacks are flexbox: Fill along the direction is
flex: 1, and Fit Content along the direction means writing nothing. - Real tags bring keyboard focus, roles and names for free.
- SVG for drawings, PNG at 1x and 2x for pixels. Decorative images get
alt="", and a lone icon gets a name.
Quick check¶
1. A generated rule has position: absolute; left: 48px; top: 12px on a title. What happens when that title gets longer?
- It can overlap its neighbour, because nothing around it reacts to its size
- It pushes its neighbour down, because every absolute element claims its space
- It gets a smaller font, because the browser fits it into the original width
- It moves to the next row, because the list reorders itself to make more room
Absolute pins an element at exact coordinates and takes it out of the flow, so its neighbours do not know it has grown. In a flex column, a longer title makes the row taller instead.
2. How would the Figma variable space/2 with the value 8 be written as a CSS custom property?
-
--space-2: 8px; -
space/2: 8px; -
$space-2: 8; -
var(space-2): 8;
The rule: a slash becomes a hyphen, two hyphens go in front, numbers get px. You then read the value with var(--space-2) wherever you need it.
3. You define --color-text: red; in tokens.css, but the page does not change. What is the most likely reason?
- No rule uses it yet: nothing in the CSS reads
var(--color-text) - The colour is not valid: custom properties can only hold a hex value
- The name is not valid: custom properties cannot contain a hyphen
- The file is not valid: custom properties must live in the HTML
Defining a token stores the decision. It does nothing until a rule uses it with var(). That is why fix 1 replaces every hard-coded value with a var(--...).
4. What does a real button give a user that a div with onclick does not?
- Keyboard focus, the Enter key and a role for screen readers
- A coloured background, a border and a rounded corner on it
- A faster page load, a smaller file and a sharper icon on it
- A hover state, an animation and a shadow when it is pressed
A div with onclick is a button for a mouse only. The native button brings focus, keyboard activation and the right role for free. The look is the part you still have to style.
5. In a Framer stack, a layer is set to Fill along the direction. What do you write in CSS?
-
flex: 1; -
width: 100%; -
position: absolute; -
max-width: 720px;
Fill takes the free space along the direction of the stack, and that is flex: 1. Nothing about it is a percentage of the screen, and max-width is only a ceiling.
6. What is the difference between a logo exported as PNG at 1x and at 2x?
- Same size on screen, with twice the pixels per side
- Twice the size on screen, with the same pixels per side
- Same size on screen, with twice the colours per pixel
- Twice the size on screen, with twice the pixels per side
2x is for high-density screens: it shows at the same size, with twice the pixels per side, which is four times the pixels in total.
7. A toolbar has a lone trash icon, and a text button with a small plus icon beside the words \"New note\". Which treatment is right?
- The trash button gets an
aria-label, and the plus icon getsalt="" - The trash button gets
alt="", and the plus icon gets anaria-label - Both icons get
alt="", because a screen reader ignores every icon - Both icons get a long
alt, because a screen reader needs every icon
An icon beside its words is decorative: the text already says it, so alt="" skips it. A lone icon has no words at all, so its button needs a name. Text alternatives are for content, not for decoration.
8. An exporter gives you a page where everything looks perfect at one width. What is the sensible first move?
- Keep the colours and sizes, and replace the structure step by step
- Delete it all, and write every value again from the empty file
- Publish it as it is, because it matches the design at that width
- Add a fixed width to the body, so that it never leaves that size
The vocabulary of generated code is usually right: colours, sizes, paddings. What is wrong is the structure. Test it by resizing and pressing Tab, then replace the structure and keep the values.
You can now read a machine's code and fix its grammar. Next you open the code of your own Framer page with the same eyes, and start the Unit 2 project: Framer: code, components and project.