Code Connect links our components in Figma to their code counterparts. In Figma Dev Mode, a connected component shows the real code snippet (<Button variant="primary">…) instead of generated CSS. The snippets are not written by hand: a generator derives them from the component's props, its demo and its Figma config.

#The Figma config file

<component>.figma.ts sits next to <component>.tsx and holds everything Figma knows about the component exported there:

  • FIGMA_COMPONENT_URLS: the Figma component sets the component implements. The Code Connect snippet is generated from them.
  • FIGMA_COLORS: the showcase the color tokens are generated from, see Colors (Code).

Both are optional. getFigmaConfig() returns them through defineFigmaConfig, which is where prop overrides and variants go.

import { defineFigmaConfig } from "@blocks/theme/figmaConfig";

export const FIGMA_COMPONENT_URLS = [
  // component set "spinner"
  "https://www.figma.com/design/b1IDF5NSi7iiyRCrwClj34/?node-id=24519-29",
];

export const getFigmaConfig = () =>
  defineFigmaConfig({
    figmaComponents: {
      urls: FIGMA_COMPONENT_URLS,
      props: {
        // Figma names sizes by edge length in px
        size: {
          from: "size",
          values: { "16": "small", "24": "medium", "32": "large" },
        },
      },
    },
  });

Rules the generator enforces:

  • The file name is the component: spinner.figma.ts belongs to spinner.tsx, which exports Spinner. A component in a file of another name gets its own file first.
  • One file per code component, also when several components implement one Figma set.
  • FIGMA_COMPONENT_URLS is a literal array of strings, one per Figma set the component implements, usually a desktop and a mobile set. CI and the teamdebt rule read the URLs as text, so they can't be computed.
  • Each URL points at a component set or a standalone component, never at one variant or a showcase. Label it with the Figma name.
  • A desktop and a mobile set are both listed; the generator warns with the missing URL when a sibling isn't.
  • A component shown through a hook (useShowToast creates the toasts, ToastCollection renders them) names it: hook: "useShowToast". The snippet shows the hook call with the children and the props as its arguments, so children must be set.

#Connecting a component

  1. Find the set in Figma, by what it draws rather than by its name (snackbar in Figma is Toast in code). Right-click it and choose Copy link to selection.

  2. Write <component>.figma.ts with the URL.

  3. Generate the snippet:

    yarn figma:connect <component folder>
    

    This writes spinner.figma.template.ts next to the config. Templates are gitignored and regenerated in CI; never commit or edit them.

  4. Read the output. ✖ errors mean the config is wrong (unknown prop, value or Figma property, a variant instead of a set). Warnings are explained below. The count of Figma properties without a prop in the summary is information only; --verbose lists them.

  5. Read the template. What the snippet shows between the tags comes from <component>.demo.tsx, see Children from the demo; a demo the snippet can't show is listed in the output.

  6. yarn figma:connect:validate parses the template as Figma will.

The /figma-config skill runs these steps for a component or a package.

#How props are matched

The generator matches the Figma properties of the set with the component's props by name and value: a variant axis becomes the prop with the same values, a variant value that equals a boolean prop's name sets that prop (state=disabled), a text becomes a string or ReactNode prop, an instance swap a ReactNode prop, a boolean a boolean prop. Names are compared loosely (isDisabled, show label and placeholder text match disabled, label and placeholder). Interaction states like hover and focus are CSS, not props, and stay unmapped.

When a property and a prop share the name but not the values, the generator warns and prints the props entry to paste. Add it only when both sides are right in their own terms, like pixel sizes in Figma and size names in code; when one side just uses another word, rename instead. The warning is the rename backlog.

props entryUse
{ from: "size", values: { "16": "small" } }Variant values named differently from the prop's values
{ from: "state", values: { disabled: true } }Boolean prop true for one value of a variant axis
{ from: "state" }Same values under another name
{ from: "help text", when: "show help" }Text shown only while a boolean property is on
{ from: "title", values: { true: "Title" } }Value a boolean property switches on, or off with false
[{ from: "text only", … }, { from: "anatomy", … }]One entry per set when the sets name the property differently
{ value: "static" }Fixed value in every snippet; on children, text between tags
{ demo: "AdvancedAlertDemo" }children only: what that demo export renders inside
falseDrop a mapping the generator got wrong

Keys and values are checked against the real props, so a typo fails the generator. An entry with from applies to the sets that have the property and fails when none does; the same holds for a variant axis. A set that no longer exists in Figma only warns.

#Children from the demo

The content between the tags has two sources. A Figma text property named label, text, title or content maps to the children prop, so the snippet shows the text from Figma. When the set has no such property, the generator takes the children from the component's demo, <component>.demo.tsx: it picks the export that renders the component with the most distinct child components, the first on a tie. Alert gets its title, text and link from AdvancedAlertDemo, Tabs its three tabs, ContextMenu its menu items.

To show another demo, name its export in the config:

figmaComponents: {
  urls: FIGMA_COMPONENT_URLS,
  props: { children: { demo: "DismissableAlertDemo" } },
}

The generator copies the demo's children into the snippet as written, with two cleanups: attributes keep only literal values, so handlers like onClick={…} and state like isActive={active === 0} disappear.

Some demos can't be used, because the snippet couldn't import what they render: a helper from __docs__, a component defined inside the demo file, or a {…} expression as a child. The generator lists those under "Demos whose children the snippet can't show". If none of a component's demos qualifies, set the content by hand: children: { value: "Status text" }.

#One set, several components

When one Figma set is implemented by several code components (a type axis with radio and checkbox, say), each component lists the set in its own file and names its part:

figmaComponents: {
  urls: FIGMA_COMPONENT_URLS,
  variant: { type: "radio" },
}

The generator merges the files into one template that branches on the variant in Dev Mode. When the desktop and the mobile set name the part differently, list the alternatives: variant: { type: ["radio", "rating"] }. A set that lacks the axis takes the whole template; an axis no listed set has fails the config.

#Naming convention

Figma is the source of truth for names: UX decides what a component is called. Names are PascalCase on both sides, Figma properties map to props, and one responsive code component connects the desktop and the mobile set. Code-only components (VisuallyHidden, Skeleton) need no Figma counterpart. Where names still differ, the generator lists sets named differently from their component (input-field for TextField; outside the DG Framework Library a context prefix like magazine-infobox for Infobox is fine) and warns about property values the prop doesn't have. Rename in Figma or in code, and keep props overrides for cases where both vocabularies are right.

#Validation and publishing

figma-code-connect.yml runs on every pull request and master push that touches a .ts or .tsx file. Pull requests generate the affected templates (all of them when the generator changed), list them in the job summary and validate them; a failing config fails the check. Master publishes everything under the label "React", after unpublishing the nodes whose URL disappeared since the previous commit, so removing a URL or the file removes the snippet in Dev Mode. A failing config keeps its last published snippet. A set that no longer exists in Figma only warns; the teamdebt rule deadFigmaLinks reports it to the owning team.