Design & ProductARTICLE

Figma Code Connect in practice: connecting your design system to code for AI handoff

A step-by-step guide to connecting a real Button and Card to Code Connect, plugging the Dev Mode MCP Server into your AI editor, and comparing the generated screen before and after.

Figma Code Connect in practice: connecting your design system to code for AI handoff
Image: Yara Uchôa

The problem isn't new: the dev opens Dev Mode, copies the auto-generated snippet from Figma, and gets a generic that has nothing to do with the company's design system . So they rewrite everything by hand. With AI in the editor (Copilot, Cursor), the problem gets worse in a quiet way: the agent generates plausible code, but invents component names and props that don't exist in your repository. Code Connect tackles exactly that, acting as a bridge between the component in Figma and the real component in your code, becoming context for the MCP server to guide the agent with references to YOUR code, not a guess.

I'll show the path I'd take to connect two real components, a Button and a Card, publish them, plug them into the AI editor, and compare the generation before and after. I use the template files approach, which is the one recommended in the documentation for being framework-agnostic.

Prerequisites nobody tells you about upfront

Before installing anything, two real friction points for Brazilian teams:

  • Plan and seat. According to the docs, Code Connect is available on a Dev or Full seat on the Organization and Enterprise plans. If your team is on the Professional plan, that already stops the fun. It's worth confirming this before promising the feature to leadership.
  • Node installed to run the CLI, and write access to the design system repository.

Installing the CLI in the design system project:

bash
npm install --save-dev @figma/code-connect

You'll also need a Figma personal access token with Code Connect scope, exported as an environment variable (FIGMA_ACCESS_TOKEN). Keep it in an .env file outside of version control.

Connecting the Button with a template file

The idea behind the template file is that you write, in TypeScript, exactly how the snippet should appear in Dev Mode. No auto-generated code: what comes out is what you wrote. The docs provide this skeleton for a button, which is a good starting point:

ts
// Button.figma.ts
// url=https://www.figma.com/file/your-file-id/Button?node-id=123
import figma from 'figma'

const instance = figma.selectedInstance

export default {
  example: figma.code`
    <Button
      size={${instance.getEnum('Size', { Large: 'large', Medium: 'medium', Small: 'small' })}}
      disabled={${instance.getBoolean('Disabled')}}
    >
      ${instance.getString('Text Content')}
    </Button>
  `,
  imports: ['import { Button } from "components/Button"'],
  id: 'button',
}

Notice what's happening here, because this is where most of the stumbling blocks live:

  • getEnum('Size', {...}) maps the variant property called Size in Figma to the values your React component expects (large, medium, small).
  • getBoolean('Disabled') reads a boolean property.
  • getString('Text Content') pulls the instance's text.

The // url= comment at the top is what ties this file to the component in Figma. Without it pointing to the right node-id, the publish step doesn't know where to slot it in.

Stumbling block number one: property name mismatch

The most common error here is trivial and blocking: the name passed to getEnum / getBoolean must be identical to the property name in Figma, including capitalization and spaces. If in the design file the variant is called Tamanho (because the BR team named it in Portuguese) and you wrote Size, the mapping silently fails or comes back empty.

The path I'd take to avoid wasting time: open the component in Figma, look at the properties panel, and copy the exact name, accents and all. If the variant is Tamanho with values Grande / Médio / Pequeno, the map becomes:

ts
size={${instance.getEnum('Tamanho', { Grande: 'large', 'Médio': 'medium', Pequeno: 'small' })}}

The object's key is the value in Figma; the value is what goes into the code. Flipping this is the second most common stumbling block.

Connecting the Card and handling the content slot

The Card tends to be trickier than the button because it has composite content: a title, a body, maybe an image and a footer. Components with children don't fit into a single getString. The strategy that works is to map the atomic properties (elevation variant, hasImage state) and leave the children explicit in the template:

ts
// Card.figma.ts
// url=https://www.figma.com/file/SEU-FILE-ID/DesignSystem?node-id=456
import figma from 'figma'

const instance = figma.selectedInstance

export default {
  example: figma.code`
    <Card
      elevation={${instance.getEnum('Elevation', { Flat: 'flat', Raised: 'raised' })}}
      hasImage={${instance.getBoolean('Show Image')}}
    >
      <Card.Title>${instance.getString('Title')}</Card.Title>
      <Card.Body>${instance.getString('Body')}</Card.Body>
    </Card>
  `,
  imports: ['import { Card } from "components/Card"'],
  id: 'card',
}

This already lets you give the AI agent the subcomponent structure (Card.Title, Card.Body) that your design system actually uses, instead of a flat div.

Publishing

With both files ready, publishing makes the components visible in Dev Mode with true-to-production snippets:

bash
npx figma connect publish

According to the docs, once published, the components start showing in Dev Mode the snippets that are faithful to your design system's production code, in place of the auto-generated examples. One operational detail is worth noting: connections created via the CLI also appear in the Code Connect UI, but can only be edited via the CLI. In other words, the source of truth remains the repository, which is good for governance.

Plugging the Dev Mode MCP Server into your AI editor

The part that closes the loop is Figma's MCP server. It's the one that loads the Code Connect connections and delivers them as context to the agent. The docs are explicit: in both cases (UI or CLI), the connections are used to provide more code context via the MCP server.

In Cursor or in VS Code with Copilot, you register Figma's MCP server in the editor's MCP configuration. Once that's done, with a frame selected in Dev Mode, the agent gains access to the real references of your components.

Before and after: what changes in the generated screen

The honest test is to ask the agent for the same thing in both scenarios. A prompt like "generate the checkout screen from this frame":

| Aspect | Without Code Connect | With Code Connect | |---|---|---| | Components | generic , and made-up names | , from the design system | | Props | plausible attributes, but outside your contract | size, elevation mapped to the real variants | | Imports | guessed paths | import { Button } from "components/Button" | | Rework | rewriting almost everything | targeted adjustments |

In practice (and this is my own reading, not a measurement), the gain isn't the screen coming out "ready", it's the agent no longer hallucinating your components' API. When the import and the prop name come from your own repository, the dev reviews instead of rewriting.

What remains open

Two points deserve attention before scaling this to the entire design system. First, accessibility doesn't come for free in the snippet: if your Button requires an aria-label when it's icon-only, that needs to be in the template, otherwise the agent reproduces an inaccessible component that looks approved. Put the mandatory accessibility attributes in Code Connect's own figma.code, treating them as part of the definition of done. Second, the docs mention one-to-many connections in the UI (one design component mapped to React, SwiftUI, Vue, Compose) and automated mapping features as something still to come. For a Brazilian team with web and mobile on the same design system, that's what decides whether the effort scales or becomes maintenance debt.

Reference to get started: Code Connect Docs.

Translated from the Brazilian Portuguese original · Read the original

View profile →