Mert Özen Further With Every Line
Frontend UI/UX Design Principles — #11

From Figma to Code: Dev Mode, Auto Layout to Flexbox, Variables to Tokens and AI-Assisted Handoff with the Figma MCP Server

Mert Özen Nov 14, 2026 13 min 17 views
From Figma to Code: Dev Mode, Auto Layout to Flexbox, Variables to Tokens and AI-Assisted Handoff with the Figma MCP Server

Moving from Figma to code means carrying intent, not numbers: Auto Layout's FILL and HUG map to Flexbox, Variables to DTCG tokens, and design components to code components through Code Connect. We convert Bookstore's cart bar from Auto Layout JSON to CSS, measure copied pixels breaking at 768 pixels and in dark mode, catch drift between Figma variables and tokens, and describe the Figma MCP server's plan conditions from the official documentation.

Version note: The scripts in this post were run with Node.js 24 and the example screens were rendered and measured in Chromium 158 with Playwright 1.65; the tokens are the DTCG 2025.10 files from the previous post. Figma, Dev Mode, Code Connect and the Figma MCP server were not run for this post; what is said about them is based on Figma's official documentation as of November 2026, and the JSON samples coming from Figma were written by hand with the documented field names.

Guess First

For Bookstore, the online bookstore used as the shared example across this site's series, the designer has drawn the mobile cart bar from post 09 in Figma, inside a 390-pixel phone frame, and marked it "Ready for dev". The developer writing this bar in bookstore-web opened Dev Mode and moved the sizes and colours they saw into CSS as they were:

/* sizes and colours copied as they appear in a 390 px design frame */
.cart-bar { position: fixed; bottom: 0; left: 0; width: 390px; height: 72px; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; background: #FFFFFF; }
.count { font-size: 14px; color: #6B6F75; }
.total { font-size: 16px; font-weight: 700; color: #202224; }
.checkout { width: 138px; height: 48px; display: flex; align-items: center; justify-content: center; border-radius: 6px; background: #3366D6; color: #FFFFFF; font-weight: 600; }

On a 390-pixel phone the screen is identical to the design. How wide is the bar on a 768-pixel tablet, and where does Checkout sit? What colour is the bar when the user is in dark mode? Is #3366D6 the same colour as the --color-action we moved into tokens in the previous post? When the designer changes the shade of the blue, who notices this line, and how?

In this last post we look at the other end of the tokens, the design tool. The goal is not to teach Figma; it is to read the design file with a developer's eye and measure what gets lost on the way to code.

Measure first

The script does three things. It converts the bar's Auto Layout settings in Figma, written as JSON with the property names of Figma's Plugin API, into CSS and lists the values not bound to a variable. It renders the same bar built from Auto Layout and from copied pixels at 390 and 768 pixels wide and in dark mode. Finally it compares a file in the shape of Figma's REST API variables response with the DTCG tokens from the previous post:

npm run tokens
node scripts/11-handoff.mjs
--- Auto Layout -> CSS
CartBar: display: flex; flex-direction: row; justify-content: space-between; align-items: center; gap: var(--space-4); padding: var(--space-3) var(--space-4) var(--space-3) var(--space-4); flex: 1 1 0; background: var(--color-surface-raised);
Summary: display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; gap: 0px; flex: none;
Count: color: var(--color-text-muted); font-size: 14px; font-weight: 400;
Total: color: var(--color-text); font-size: 16px; font-weight: 700;
Checkout: display: flex; flex-direction: row; justify-content: center; align-items: center; padding: 0px var(--space-8) 0px var(--space-8); flex: none; height: 48px; border-radius: 6px; background: var(--color-action);
Label: color: var(--color-on-action); font-size: 16px; font-weight: 600;
values not bound to a variable: Checkout.height = 48, Checkout.cornerRadius = 6
--- rendered
  auto layout at 390 px: bar 390 px wide, checkout 138x48, 16 px from the right edge
  auto layout at 768 px: bar 768 px wide, checkout 138x48, 16 px from the right edge
  auto layout at 390 px: dark: bar background oklch(0.4 0.008 264)
  copied values at 390 px: bar 390 px wide, checkout 138x48, 16 px from the right edge
  copied values at 768 px: bar 390 px wide, checkout 138x48, 394 px from the right edge
  copied values at 390 px: dark: bar background rgb(255, 255, 255)
--- variables vs tokens
colour variables: 20, in both: 18, only in Figma: white, color/brand-accent
different values: blue/600 (light) Figma #3366d6, code #3766d5; color/action (light) Figma #3366d6, code #3766d5
semantic colours only in code: 10 (color/border, color/border-strong, color/link, color/success-text, ...)

At 390 pixels both bars are the same. At 768 pixels the copied bar stays at 390 pixels and Checkout sits 394 pixels from the right edge; in dark mode the bar stays white. The bar generated from Auto Layout fills the screen at every width and takes the raised surface tone from post 09 in dark mode. The variable comparison also shows where the copied #3366D6 came from: blue/600 in the Figma file is one shade off the token in code.

Reading Figma with a Developer's Eye

There are a few concepts a developer needs in a Figma file. A frame is a box that other layers go into; a screen can be a frame, and so can a button. A component is the main definition of a reused piece of design; an instance is one use of that component, and instances change when the main definition changes. Variants are different versions of a component: "Button, Primary, Disabled" and so on. In code these are a component, one use of it, and its inputs or states. In frames without Auto Layout, constraints decide how children behave when the parent grows (pinned left, right, centre, or scaled); that is closer to absolute positioning in CSS.

Dev Mode is Figma's workspace for developers. According to Figma's help page, it shows the properties of the selected layer and an automatically generated code snippet, and lets you inspect applied variables and styles, the designer's notes and measurements, sections marked "ready for dev", and comparisons of changes in a frame's history. Dev Mode is available on the Professional, Education, Organization, Government and Enterprise plans; notifications need a Full or Dev seat.

The code Dev Mode shows is a starting point, not a result to copy. The two mistakes of copied CSS are visible in the measurement's "copied values" lines: because the design was drawn in a 390-pixel frame, widths come over as fixed numbers, and when colours are taken as hex values instead of variables, the theme information is lost. What you need to read is not the number but the intent behind it: "this bar fills the screen", "this button is as wide as its content", "this colour is the primary action colour".

Auto Layout ≈ Flexbox

Auto Layout is the feature that lines up a frame's children along one axis and manages the space between them, the padding and the alignment. That description is almost exactly CSS Flexbox; with the property names of Figma's Plugin API, the mapping is:

Figma (Plugin API)CSS
layoutMode: HORIZONTAL / VERTICALdisplay: flex, flex-direction: row / column
itemSpacinggap
paddingTop/Right/Bottom/Leftpadding
primaryAxisAlignItems: MIN / CENTER / MAX / SPACE_BETWEENjustify-content
counterAxisAlignItems: MIN / CENTER / MAX / BASELINEalign-items
layoutWrap: WRAPflex-wrap: wrap
layoutSizingHorizontal: FILL (on the main axis)flex: 1 1 0
layoutSizingHorizontal: HUGflex: none, width from the content
layoutSizingHorizontal: FIXEDwidth and flex-shrink: 0

In Figma's interface these three sizing options appear as "Hug contents", "Fill container" and "Fixed". According to the Plugin API documentation, HUG is valid only on Auto Layout frames and text layers, and FILL only on children of an Auto Layout frame. Our converter applies these rules; what FILL means depends on the parent's direction:

// sizing: FILL grows along the parent's main axis or stretches across it, HUG takes the content size
const row = parent?.layoutMode === 'HORIZONTAL';
for (const [axis, size] of [['Horizontal', 'width'], ['Vertical', 'height']]) {
  const mode = node[`layoutSizing${axis}`];
  const main = parent && (row ? axis === 'Horizontal' : axis === 'Vertical');
  if (mode === 'FILL') {
    if (main) css.flex = '1 1 0';
    else if (parent) css['align-self'] = 'stretch';
    else css[size] = '100%';
  } else if (mode === 'FIXED') {
    css[size] = value(node, size, node[size], variables, unbound);
    if (main) css['flex-shrink'] = '0';
  } else if (mode === 'HUG' && main) {
    css.flex = 'none';
  }
}

The first part of the measurement is this converter's output. The bar itself is FILL, as wide as the screen; the summary and the button are HUG, as wide as their content; between them, space-between. The copied CSS's width: 390px and width: 138px reduce that intent to two fixed numbers; 138 happens to be right (the button is as wide as its content), 390 is right on only one screen. Flexbox itself is covered in the Flexbox posts of the CSS3 series; the counterpart of Figma's newer grid Auto Layout is CSS Grid.

From Variables to Tokens

Figma Variables are named values in the design file: colour, number, string and boolean. Collections group variables, and modes hold a collection's values in different contexts. In the previous post's terms: a primitive collection is a single-mode palette, a semantic collection is a set with "Light" and "Dark" modes; a mode in Figma corresponds to a modifier context in the DTCG resolver. The slash in variable names (color/action) provides the grouping; in DTCG it is color.action, in CSS --color-action.

That link is why the CSS in the measurement doesn't contain a single hex value: the bar's padding and gap are bound to the space/3 and space/4 variables, its colour to color/surface-raised, and the converter turns every bound value into the matching token's CSS variable. The two unbound values, the button's 48-pixel height and 6-pixel corner radius, are listed separately. Our tokens already have button.min-height and button.radius; those are the values that should be bound to variables in the design file as well. According to Figma's help page, every variable can also get a code syntax for Web, iOS and Android; if you write var(--color-action), Dev Mode's code snippet shows the token instead of a hex value.

The official way to get variables out of a file is the variables endpoints of Figma's REST API; according to the documentation, they are available only on the Enterprise plan, and reading requires the file_variables:read scope. The tz import command of Terrazzo, which we used in the previous post, can also generate a DTCG resolver file from a Figma file, but according to Terrazzo's documentation, variables again require the Enterprise plan; styles can be imported on lower plans too. We didn't find a built-in feature in Figma's official help pages that exports variables directly as DTCG; community plugins fill that gap, and how closely each follows 2025.10 needs to be checked separately.

Whichever way they are fetched, the real question is the same: do design and code use the same values? The script compares the file in Figma's response shape with the tokens and finds three kinds of difference. Of the 18 colours that exist on both sides under the same name, one differs: the designer moved blue/600 to #3366d6, while code has #3766d5; through the alias, the difference also reaches the light theme value of color/action. color/brand-accent exists only in Figma; it should either be added to code or removed from the design. Ten semantic colours such as color/border and color/link exist only in code; not having them defined in this file may mean the designer uses free colours on those screens. If this comparison runs regularly in CI, the answer to Guess First's "who notices when the designer changes the shade?" becomes a build warning.

Code Connect: Mapping Design Components to Code Components

Tokens link values; Code Connect links components. According to Figma's description, Code Connect maps components in code to their counterparts in Figma, so that for a selected button instance Dev Mode shows the use of the project's own component instead of generic, automatically generated code, and the Figma MCP server uses the same mapping. It has two forms: a UI set up from inside Figma by connecting to GitHub or to file paths, and a CLI that runs in the terminal and supports property mappings through template files. The README of figma/code-connect on GitHub lists React, Storybook, SwiftUI and Jetpack Compose, plus an HTML integration that covers Angular and Web Components. Code Connect is available on Professional and higher plans with a Full or Dev seat.

What that means for Bookstore: when the "Button / Primary / Disabled" instance in the design is selected, Dev Mode should show the Angular component that follows the state set from post 07 and its use of aria-disabled, not a div with hex colours. Without this mapping every developer writes the same button again, and the focus and disabled bugs we measured in 07 come back every time.

AI-Assisted Handoff with the Figma MCP Server

The Figma MCP server, which works over the Model Context Protocol, the open protocol AI assistants use to connect to external tools and data sources, gives an AI coding assistant the structure, variables and a screenshot of the selected design. Figma's developer documentation describes two options: the recommended remote server connects to Figma's hosted endpoint at https://mcp.figma.com/mcp without the desktop app and offers the broadest set of features; the desktop server runs locally through the Figma desktop app and, according to the documentation, is meant for specific organisation and enterprise needs and requires a Dev or Full seat. New features such as writing to the design file are available only on the remote server.

Plan and seat conditions have to be checked at the time of writing, because even the sources differ. In November 2026, according to the table in the developer documentation, the Starter plan and View and Collab seats on paid plans allow up to 20 tool calls per month; for Full and Dev seats the limit is 200 per day and 10 per minute on the Professional plan, and 600 per day and 20 per minute on Enterprise. On the same date, the README of figma/mcp-server-guide on GitHub says 6 calls per month for Starter and View/Collab. The limits apply to tools that read from the design file; some tools that write to the file are exempt.

What does it do well? According to the guide, get_design_context returns a structured representation of the selection, in React and Tailwind form by default; get_variable_defs extracts the variables and styles used in the selection, and get_screenshot provides a visual reference. With that context the assistant doesn't have to guess Auto Layout, spacing and hierarchy from a screenshot. Where does it produce code that doesn't fit your own library? In the guide's own words, without Code Connect the model is guessing at components. In a project that uses Angular and its own tokens, the default React and Tailwind output is only a starting point; the assistant may write the button as a new div instead of your bookstore-button component, and use hex values instead of tokens. That's why the guide's recommendations match the subject of this post: make everything reused a component, connect components to code with Code Connect, make colours, spacing and corner radii variables, let Auto Layout carry the responsive intent, name layers meaningfully, and select small pieces instead of large screens. Code generated through MCP should also go through the measurements in this series: contrast, states, target size, overflow.

Handoff Checklist: Token-Perfect, Not Pixel-Perfect

A "pixel-perfect" goal produces a screen that is right at one width, in one theme and in one state; the copied bar in the measurement was exactly that. A token-perfect goal is a screen where every value is tied to a decision: the colour is a semantic token, the spacing a spacing token, the width a sizing intent. Before calling a design "ready for dev", asking the questions that came out of the nine posts of this series is enough:

  • Are all states drawn: hover, focus-visible, active, disabled, loading, error, selected (07)?
  • Are there empty, no-results and error screens, and does each show a way forward (07, 08)?
  • How does the component behave on narrow and wide screens; is the FILL, HUG and FIXED intent clear, and is the narrowest width 320 pixels (09)?
  • Is every colour, spacing and corner radius bound to a variable; is the list of unbound values empty (10, 11)?
  • Have the contrast of text and controls and the touch targets been measured; is the dark theme drawn as a separate mode (04, 09)?
  • In forms, are the texts of labels, hints and error messages written (06)?
  • Are keyboard order, where focus goes, and notes for screen readers added as annotations (07)?

In Production

Pick a single source. Is the source of the tokens the code or the design file? It can be either, but not both. For Bookstore the source is the DTCG files in the repository; the Figma variables are updated from them and the script reports differences. The opposite is also a valid choice, as long as the direction is one-way.

The conditions of Figma and its tools change often. The plan, seat and limit information in this post dates from November 2026; the MCP server's capabilities and pricing may change during the beta. Check the current documentation before proposing a process to a team.

Review AI-generated code like code written by hand. The assistant having access to the design context doesn't mean the code it produces is accessible, uses tokens and uses the project's components; the scripts in this series are a good start for automating that check.

Common Pitfalls

First, copying sizes and hex values from Dev Mode as they are. The number is right in one frame; the intent (FILL, HUG, token) is right on every screen.

Second, accepting values in the design that aren't bound to a variable. Every unbound value comes back as a magic number in code.

Third, keeping design and code tokens alive as two separate sources. The difference only shows when someone measures it; automate the comparison.

Fourth, expecting component code from AI without Code Connect. If the model doesn't know your library, it guesses.

Fifth, reducing handoff to a single screenshot. States, empty and error screens, responsive behaviour and accessibility notes are part of the design too.

A Small Experiment

★☆☆ In cart-bar.figma.json, bind Checkout's height and cornerRadius to variables: add two FLOAT variables called button/min-height and button/radius to variables.figma.json. Verify that the script's "values not bound to a variable" line becomes empty and that two new tokens appear in the generated CSS.

★★☆ Add support for layoutWrap: WRAP and counterAxisSpacing to the converter and write the category strip from post 09 as Auto Layout JSON. Measure that at 320 pixels the strip wraps and that the space between rows comes out as row-gap.

★★★ Reverse the variable comparison: generate JSON in the shape of the Figma REST API's POST variables body from the DTCG tokens, and show the change that would close the blue/600 difference in that body. Without calling the API, only check that the generated body matches the documented fields.

The series ends here. Over eleven posts, from Bookstore's checkout to its cart, from its catalogue to its token files, we tried to tie every decision to a measurement: heuristics, hierarchy, typography, colour and contrast, spacing, forms, states, navigation, responsive design, the design system and handoff. The natural next steps are the Accessibility (a11y) series, with the criteria in their full, testable form, and the Analytics and Measurement series, which covers measuring the effect of these decisions on users.