The useful part is not generating more code. It is removing a guess.
Code Connect does not turn a Figma file into production code. It tells Figma and its MCP server which real component to use, how design properties map to React props, and which import and usage pattern the codebase expects. That removes a large part of the guesswork, while Storybook, tests and human review still decide whether the implementation is correct.
This distinction is why I value Code Connect. Without it, a design-to-code tool can see a blue rectangle, some text and a radius. It still has to guess whether that is an existing Button, which variant it represents and how the component is imported. Code Connect gives it those missing facts.
There is also a timing detail that matters. On 17 August 2026, Figma stopped actively maintaining its framework-specific parsers. The recommended format is now a framework-independent .figma.ts template. A lot of React examples still show .figma.tsx and figma.connect(...), but that is the legacy API, not the setup I would start today.
1. What does Code Connect actually connect?
Code Connect stores an explicit relationship between a published Figma component and a component in a codebase. When a developer selects an instance in Dev Mode, Figma can show the production import and a usage example based on that instance’s current properties. When an MCP client requests the same design context, the mapping can travel with it.
For a CLI mapping, Figma’s MCP server places the implementation detail inside a synthetic <CodeConnectSnippet> in the model’s context. That context can include the current design property values, import statements, the component snippet, source paths and any additional instructions. The agent still writes the final code, but it is no longer choosing the component from appearance alone.
Code Connect does not do any of these jobs:
- synchronise Figma variables with repository tokens;
- create or maintain the React component;
- prove that the rendered component matches the design;
- run accessibility, interaction or visual tests;
- decide whether the component API is a good one;
- guarantee that an AI agent will use the context correctly.
I think of it as implementation metadata. It makes the intended path visible to Dev Mode and MCP, but it does not replace the path.
The token layer needs to exist independently. I covered the repository-owned token contract, Figma variables, Tailwind CSS and dark mode in Figma design tokens with Tailwind CSS. This article starts from the point where the component already consumes those semantic tokens.
2. Should you use Code Connect UI or CLI?
The UI and CLI publish into the same Code Connect system. They are different authoring workflows, not different products, and they can coexist. A CLI-created connection appears in the UI, but it remains editable through the CLI.
| Code Connect UI | Code Connect CLI | |
|---|---|---|
| Where it runs | Inside Figma | Inside the component repository |
| What it maps | Component path and name, with optional MCP instructions | Imports, property mappings and dynamic usage examples |
| Code access | GitHub connection or manually entered paths | The local repository |
| Best fit | A quick, language-independent connection | A versioned component contract owned with the code |
| Current limitation | No property mapping or dynamic examples | Requires CLI setup, review and publishing |
The official comparison is quite direct about the important limitation: the UI does not currently support property mapping or dynamic examples. It can tell MCP that a design component relates to Button, but it cannot express that Figma’s Primary value means the React prop variant="primary".
That makes the CLI the right choice for the example in this article. The mapping sits beside the component, goes through code review and changes in the same pull request as the React API. More importantly, it can describe the exact snippet I want a developer or agent to see.
The UI still has a useful place. If a design team needs to connect one component to several implementations, or wants to add component-specific MCP instructions without maintaining templates, it is the faster entry point. I would not use that convenience to hide an unstable code API, though. A shallow connection to the wrong abstraction only makes the wrong abstraction easier to find.
3. What has to exist before the connection is useful?
A useful mapping begins with two clear contracts. Figma needs a published library component with deliberate properties, and React needs a stable component API. If either side is vague, Code Connect only records the vagueness.
For this walkthrough I use one neutral Button. In Figma, I would build it with Auto Layout and expose these component properties:
| Figma property | Type | Values or role | React target |
|---|---|---|---|
Variant |
Variant | Primary, Secondary, Danger |
variant |
Size |
Variant | Small, Medium, Large |
size |
State |
Variant | Default, Disabled, Loading |
state |
Label |
Text | Visible button text | label |
Show leading icon |
Boolean | Controls icon layer visibility | Whether leadingIcon is emitted |
Leading icon |
Instance swap | Selects a preferred icon instance | leadingIcon |
There are two details here that save trouble later.
First, the Figma names and React names do not have to match character for character. They have to match in meaning. Figma values are often title-cased for designers, while a TypeScript union normally uses lowercase values. The template is where I translate between them.
Second, a Figma boolean property controls layer visibility. I use it for Show leading icon because that is genuinely a visibility decision. I do not use a boolean called Disabled and another called Loading while also keeping a State variant. That would allow impossible combinations and force the template to decide which one wins.
The setup on the Figma side is:
- Create the Button component set and apply semantic variables to colour, typography, spacing and radius.
- Bind
Labelto the text layer. - Bind
Show leading iconto the icon layer’s visibility. - Bind
Leading iconas an instance-swap property and limit its preferred values to the icon library. - Publish the component to the team library.
- Copy the link to the main Button component, including its
node-id. That exact URL identifies the Code Connect target.
If the icon should appear as real code inside the generated Button usage, the icon component also needs its own connection. getInstanceSwap() can locate the swapped instance, but executeTemplate() needs a connected template to render the production icon snippet.
4. How should the React Button express the same contract?
The React component should stay boring. It renders a native button, derives native disabled behaviour from its state and accepts the same small set of decisions that exist in Figma.
import type { ButtonHTMLAttributes, ReactNode } from "react";
export type ButtonVariant = "primary" | "secondary" | "danger";
export type ButtonSize = "small" | "medium" | "large";
export type ButtonState = "default" | "disabled" | "loading";
export type ButtonProps = Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
"children" | "className" | "disabled"
> & {
label: string;
variant?: ButtonVariant;
size?: ButtonSize;
state?: ButtonState;
leadingIcon?: ReactNode;
};
export function Button({
label,
variant = "primary",
size = "medium",
state = "default",
leadingIcon,
type = "button",
...buttonProps
}: ButtonProps) {
const isLoading = state === "loading";
const isDisabled = state === "disabled" || isLoading;
return (
<button
{...buttonProps}
type={type}
disabled={isDisabled}
aria-busy={isLoading || undefined}
className={`button button--${variant} button--${size}`}
data-state={state}
>
{isLoading ? (
<span className="button__spinner" aria-hidden="true" />
) : leadingIcon ? (
<span className="button__icon" aria-hidden="true">
{leadingIcon}
</span>
) : null}
<span>{label}</span>
</button>
);
}The default type="button" is deliberate. A reusable Button should not submit the nearest form merely because the caller did not remember the browser default. The loading state keeps the accessible name, adds aria-busy and disables repeat activation. The decorative leading icon is hidden from assistive technology because the label already names the action.
The CSS behind this component consumes semantic tokens. Code Connect does not need to know whether button--danger resolves through Tailwind theme variables, CSS modules or another implementation. Its job is to map the design decision to variant="danger", not to repeat the styling system.
5. Where do React, Storybook and Code Connect live?
I keep the three artefacts together:
libs/shared/ui/src/Button/
Button.tsx
Button.stories.tsx
Button.figma.ts
The names are similar, but their responsibilities are not:
Button.tsxis the production component and its public API.Button.stories.tsxrenders meaningful states, documents controls and exercises behaviour.Button.figma.tstranslates Figma properties into a production usage snippet.
Storybook is not the Code Connect mapping. The older direct Storybook integration belongs to the legacy parser path, and Figma now labels it that way. I still consider Storybook essential because it answers the question Code Connect cannot: does the real component behave correctly in each state?
For this target setup, I would pin Storybook, @storybook/react-vite and each addon to the same 10.5.10 release. The addons have separate jobs:
@storybook/addon-docsfor the component API and generated documentation;@storybook/addon-a11yfor axe checks;@storybook/addon-vitestto run component and interaction tests in the UI and CI;@storybook/addon-themesto exercise the light and dark token modes.
I would not install those names and then claim the component has been tested. The addons only provide the machinery. The stories and CI command are what make the checks real.
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, fn } from "storybook/test";
import { Button } from "./Button";
const meta = {
title: "Shared UI/Button",
component: Button,
tags: ["autodocs"],
args: {
label: "Continue",
variant: "primary",
size: "medium",
state: "default",
onClick: fn(),
},
parameters: {
a11y: { test: "error" },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {};
export const Danger: Story = {
args: { variant: "danger" },
};
export const Loading: Story = {
args: { state: "loading" },
play: async ({ canvas }) => {
const button = canvas.getByRole("button", { name: "Continue" });
await expect(button).toBeDisabled();
await expect(button).toHaveAttribute("aria-busy", "true");
},
};
export const Clickable: Story = {
play: async ({ args, canvas, userEvent }) => {
await userEvent.click(canvas.getByRole("button", { name: "Continue" }));
await expect(args.onClick).toHaveBeenCalledOnce();
},
};The story args should cover the same vocabulary as Figma. If Storybook accepts size="compact" while the design library says Small and the component type says small, that is not flexibility. It is drift with three spellings.
For dark mode, @storybook/addon-themes can apply data-theme="dark" to the preview root using withThemeByDataAttribute. That keeps the Storybook environment aligned with the token selector described in the companion article. I want to see the same Button stories under both modes, not maintain a second set of dark-only stories.
6. How do you configure Code Connect CLI 2.0.0?
Code Connect is available to a Dev or Full seat on Figma Organization and Enterprise plans. The current CLI guide also requires Node.js 18 or newer and a personal access token with Code Connect set to Write and File content set to Read. Keep that token in FIGMA_ACCESS_TOKEN or a CI secret. Do not put it in figma.config.json or a shell history command.
Figma recommends installing @figma/code-connect@latest. On 31 August 2026, that resolves to 2.0.0. I would still pin the resolved version in the repository so a release does not change template behaviour halfway through a migration.
The project configuration is small:
{
"codeConnect": {
"include": ["libs/shared/ui/src/**/*.figma.ts"],
"label": "React",
"language": "jsx"
}
}language is jsx even though the template itself is TypeScript. It describes the snippet Figma displays, not the file extension used to author the template. tsx is a reasonable guess from the filename, but jsx is the current documented value for React snippets.
For editor type checking, add @figma/code-connect/figma-types to the TypeScript project’s compilerOptions.types. If the repository already has a restricted types array, merge it rather than replacing the existing entries:
{
"compilerOptions": {
"types": ["vite/client", "@figma/code-connect/figma-types"]
}
}
7. How do you write the current Button.figma.ts template?
A template has three important pieces. The metadata comment points to the published Figma component, property accessors read the selected instance, and the default export defines the displayed snippet, imports and stable Code Connect ID.
The first line must contain the exact component URL returned by Copy link to selection, including node-id. The FILE_KEY URL below is illustrative so I do not publish a private file identifier. In a real template, paste the copied URL on that line without changing its query parameters.
// url=https://www.figma.com/design/FILE_KEY/demo?node-id=1-2
// source=libs/shared/ui/src/Button/Button.tsx
// component=Button
import figma from "figma";
const instance = figma.selectedInstance;
const label = instance.getString("Label");
const variant = instance.getEnum("Variant", {
Primary: "primary",
Secondary: "secondary",
Danger: "danger",
});
const size = instance.getEnum("Size", {
Small: "small",
Medium: "medium",
Large: "large",
});
const state = instance.getEnum("State", {
Default: "default",
Disabled: "disabled",
Loading: "loading",
});
const showLeadingIcon = instance.getBoolean("Show leading icon");
const leadingIcon = instance.getInstanceSwap("Leading icon");
const leadingIconProp = showLeadingIcon
? figma.helpers.react.renderProp(
"leadingIcon",
leadingIcon.executeTemplate().example,
)
: null;
export default {
example: figma.code`<Button${figma.helpers.react.renderProp("label", label)}${figma.helpers.react.renderProp("variant", variant)}${figma.helpers.react.renderProp("size", size)}${figma.helpers.react.renderProp("state", state)}${leadingIconProp} />`,
imports: ['import { Button } from "@shared/ui"'],
id: "shared-ui-button",
metadata: {
nestable: true,
},
};The compact figma.code expression is deliberate. The helper knows how to render strings as quoted JSX props and a connected icon as leadingIcon={<Icon />}. It also avoids building snippets with ordinary string concatenation, which the template API warns against because snippets are represented as structured result sections internally.
getEnum() is where the design and code vocabularies meet. getString() carries the instance label. getBoolean() decides whether the icon prop belongs in the usage at all. getInstanceSwap() returns the chosen icon instance, then executeTemplate() renders that icon through its own Code Connect template.
The id is not a display label. Other templates can use it to locate nested connected instances, so I treat it as a stable API. Renaming the React component does not automatically mean the Code Connect ID should change.
One more practical point: source is the path shown in Figma, while imports is the code shown to the developer and supplied to MCP. I use the public package import in imports, not the template’s relative filesystem import. An agent should learn how application code consumes the library, not how the template happens to sit beside it.
8. How do you validate a mapping before publishing it?
Publishing should be the last step, not the feedback loop. Code Connect 2.0.0 can preview a template locally, inspect available properties, render one chosen combination, enumerate combinations and collapse duplicate snippets.
npx figma connect preview \
libs/shared/ui/src/Button/Button.figma.ts \
--inspect
npx figma connect preview \
libs/shared/ui/src/Button/Button.figma.ts \
--props Variant=Primary Size=Medium State=Default \
"Show leading icon=true"
npx figma connect preview \
libs/shared/ui/src/Button/Button.figma.ts \
--unique
npx figma connect publish --dry-run--inspect is the quickest way to catch a property name that differs by one word. --props proves the important examples deliberately. --all is useful on a small component, but the combinations grow quickly, so --unique is often the better signal. It renders distinct snippets and reports how many property combinations produced each one.
The preview command also validates JSX and TSX snippets with Prettier where the language is supported. That catches malformed output before it reaches Dev Mode. The publish --dry-run validates what would be uploaded without changing the existing connection.
My minimum Button checks would be:
- primary, medium and default with no icon;
- secondary, small and default with a connected icon;
- danger, large and disabled;
- primary, medium and loading;
- a swapped icon whose own template produces the expected import.
After the real publish, I still open the component in Dev Mode and inspect those states. Local preview proves the template output. Dev Mode proves that the right Figma node, label and published connection meet.
9. How should an existing .figma.tsx setup be migrated?
The old React parser put executable JSX mappings in .figma.tsx and usually called figma.connect(...). Those files may continue to work for now, but Figma no longer actively maintains the parsers. I would not migrate an entire library by changing file extensions or deleting the source in one command.
The CLI has a migration command, and its output is explicitly a starting point rather than production-ready Code Connect:
npx figma connect migrate \
--file libs/shared/ui/src/Button/Button.figma.tsx \
--outDir .code-connect-migration \
--batch none
npx figma connect preview \
.code-connect-migration/Button.figma.ts \
--uniqueI leave out --delete on purpose. The sequence I would use is:
- Commit or otherwise preserve the current working tree.
- Limit the migration to one Button.
- Write generated templates to
.code-connect-migration. - Review every property accessor, import and branch.
- Simplify generated helper code where the React prop type is already known.
- Preview the meaningful property combinations.
- Publish under a temporary label and inspect the result in Dev Mode.
- Move the reviewed template beside
Button.tsx. - Update the production include glob.
- Remove the old
.figma.tsxonly after the replacement is verified.
When the whole library is migrated, parser-only configuration such as parser, importPaths, paths and imports can go. Removing those fields after the first Button would be premature if the rest of the library still depends on them.
There is a useful architectural improvement hidden in this migration. The old parser tried to understand a framework-specific source file. The new template says exactly what should be shown. It is less magical, but the result is easier to review because the output contract is visible.
10. How do Remote MCP and desktop MCP use the connection?
UI versus CLI decides how the mapping is authored. Remote versus desktop decides how an MCP client reaches Figma. These are separate choices.
| Remote MCP server | Desktop MCP server | |
|---|---|---|
| Endpoint | https://mcp.figma.com/mcp |
http://127.0.0.1:3845/mcp |
| Authentication | Figma OAuth in a supported client | Local Figma desktop session |
| Desktop app | Not required | Required and must remain running |
| Design input | Figma link | Figma link or current desktop selection |
| Tool coverage | Figma’s recommended option with the broadest tool set | Intended for specific local Organization and Enterprise use cases |
| Code Connect | Included in design context | Must be enabled in the desktop MCP settings |
I would use Remote MCP as the default because Figma recommends it and it removes the local application dependency. It is also easier to make a comparison repeatable because the design node arrives through a link.
The desktop server is useful when I am already in Dev Mode and want the current selection to be the prompt input. I can select a frame or component instance and ask the client to implement the selection without copying a link. It also accepts link-based prompts, which matters when comparing it with Remote MCP.
There is one mapping detail to make explicit when a library publishes several labels. With Remote MCP, I instruct the agent to pass the exact Code Connect label, such as React, through the clientFrameworks parameter on the Figma tool call. The desktop server instead uses the Code Connect mapping selected in Dev Mode. A Remote run with the wrong label and a desktop run with the right selection are not equivalent tests.
In either transport, Code Connect changes the context, not the agent. For a CLI mapping, the MCP response can include the real import, the generated Button usage and its current property values. The coding agent still has to locate the package, understand the surrounding feature, decide where the component belongs, preserve accessibility and validate the change. Figma’s own explanation of what MCP sends and what the agent does makes this separation clear.
This is the pipeline I want:
Figma instance
-> component properties and semantic variables
-> Code Connect import and usage template
-> Remote or desktop MCP context
-> coding agent working inside the repository
-> Storybook, type checks, tests and human review
Every arrow adds information. None of them removes responsibility from the final review.
11. What can I claim about MCP accuracy before the migration?
Code Connect gives MCP richer implementation context. That mechanism is documented and inspectable. I have not run a controlled before-and-after comparison with the new template format yet, so I cannot honestly attach an accuracy number to it or say the migration improved production output.
The narrower claim is enough: the connected request contains facts that are absent from an unconnected one. It can name the existing Button, its import and the props for the selected Figma instance. That reduces what the agent has to infer, but it does not prove the agent will use the information well.
When the migration begins, I will compare one fixed frame and repository commit with and without the reviewed mapping. Each condition needs a fresh session with the same client, model, prompt and validation commands. Remote and desktop MCP must be recorded separately, because changing the transport changes the condition.
I will judge the result by component reuse, correct props, semantic-token reuse, native button behaviour and the manual corrections left in the diff. A screenshot that looks close is not enough evidence, and a handful of runs will still be a case study rather than a universal percentage.
12. How do you keep the connection healthy?
A Code Connect mapping is part of the component API. If ButtonProps changes and Button.figma.ts does not, Dev Mode and MCP start teaching an old usage pattern with complete confidence. That is worse than having no connection because it looks authoritative.
I would keep it healthy with a few ordinary rules:
- One owner for the contract. A change to a shared component’s public props reviews its Figma properties, stories and template in the same pull request.
- Local parsing on every change. Run
figma connect parseorpreviewfor changed templates, and fail on unreadable files in CI. - Dry-run before protected publishing. Validate the documents in CI, then publish from a protected release job rather than every feature branch.
- Temporary labels for migration. Test templates without overwriting the existing parser connection.
- Secrets stay outside the repository.
FIGMA_ACCESS_TOKENbelongs in a local environment or secret store with only the required scopes. - Regression prompts stay small. Keep one or two stable Figma frames and prompts that reveal whether component reuse has slipped.
- Storybook remains executable. A Code Connect snippet is documentation. Storybook and tests are the evidence that the documented states still work.
This is the part that changes the design-to-development pipeline for me. The useful connection is not from pixels to generated JSX. It is from a deliberate Figma property, through a reviewed template, to an existing typed component whose behaviour is already visible and testable.
That still requires a good design system. Code Connect does not rescue unclear tokens, inconsistent component properties or a React library with three Buttons. Once those foundations exist, though, it gives Dev Mode and MCP something they have historically lacked: the exact implementation choice the team has already made.
Sources and further reading
- Figma’s Code Connect introduction and current CLI quick start.
- The current template file format, Template API and CLI reference.
- Figma’s parser-to-template migration guide.
- Figma’s explanation of Code Connect context inside MCP.
- Setup guidance for the Remote MCP server and desktop MCP server.
- Storybook 10.5 documentation for typed stories, interaction tests, accessibility tests and theme data attributes.
- Figma’s public Simple Design System repository for a larger reference implementation.
- My Heelix project write-up for the experience that led me to this workflow, and the Appliances Online project for why Storybook remains part of the boundary.