Skip to content

Design-to-code

A Figma instance in a mock should map cleanly to a component import in code — the same name, the same prop set, the same visual identity. This page covers the conventions that make the mapping work and the Figma Code Connect setup that automates it.

If you're a designer hand-shaking with engineering, or an engineer reading from a mock, this is the page.

The naming convention

Figma frame name → code component:

Button / Primary / Md         →  <Button variant="primary" size="md" />
Pill / Green / Default        →  <Pill tone="green" />
Eyebrow / Mono                →  <Eyebrow mono />
DisplayHeading / Hero / H2    →  <DisplayHeading size="hero" as="h2" />
BoardConsentCard / Default    →  <BoardConsentCard … />

The pattern: <Component> / <Variant> / <Size or State>. Each / is a level in the Figma variant axis. The leftmost token is always the component name; the rest are props in left-to-right precedence.

When a frame doesn't match a component

Three possibilities, in order of likelihood:

  1. The component exists; the naming drifted. Most common. Open the Figma file and rename the frame to match the canonical component name. The rest of the mapping falls out.
  2. The frame is a one-off composition. Pair existing components rather than build a new one. Use a recipe page as the model.
  3. It's a genuinely new component. Propose it via Governance — the bar is "appears in ≥ 3 real screens." One-off needs don't justify a new component.

Code Connect

Code Connect is Figma's automated design-to-code mapping. The Matter Code Connect map lives in figma-code-connect/ at the repo root.

Each canonical component has a .figma.tsx entry that maps the Figma variant axis to React props:

// Button.figma.tsx — the mapping file
import { Button } from "@matter/components";
import figma from "@figma/code-connect";

figma.connect(Button, "https://figma.com/file/…/node-id=42:317", {
  props: {
    variant: figma.enum("Variant", {
      Primary: "primary",
      Secondary: "secondary",
      Ghost: "ghost",
    }),
    size: figma.enum("Size", {
      Sm: "sm",
      Md: "md",
      Lg: "lg",
    }),
    children: figma.string("Label"),
  },
  example: ({ variant, size, children }) => (
    <Button variant={variant} size={size}>{children}</Button>
  ),
});

With Code Connect active in Figma, a designer's selection in the Inspect panel shows the matching JSX directly — no copy-paste, no guessing.

Adding a Code Connect entry

When a new component lands:

  1. Write the .figma.tsx entry. One per component; lives in figma-code-connect/.
  2. Run figma connect publish. This uploads the mapping to Figma.
  3. Verify in Figma. Select the component instance in Figma → Inspect → JSX renders.

The Code Connect skill (figma:figma-code-connect) automates step 1 — feed it a component's MDX and the corresponding Figma URL, and it scaffolds the entry.

The handoff workflow

Designer → engineering, the canonical flow:

  1. Designer ships a mock. Every component instance uses a canonical variant (matched by frame name).
  2. Designer annotates the mock. For each instance, the variant maps to the component name. Code Connect surfaces this in the Inspect panel.
  3. Designer hands off via PR description. The handoff doc includes:
    • Link to the Figma frame.
    • List of components used (auto-derived from Code Connect when available).
    • Link to the matching recipe page (if the screen mirrors one).
    • Any new variants / tokens proposed (separate PRs in @matter/tokens or @matter/components first).
  4. Engineering implements. Imports components, reads tokens (no inline hex), composes from documented patterns.
  5. Engineering verifies parity. Compare the live render to the Figma mock at the canonical breakpoints. Spot the difference; fix the implementation.

If you're proposing a new variant or token as part of the handoff, those land in @matter/tokens or @matter/components first (with the governance proposal), then the consumer migration ships.

Token names in handoff

Engineering reads design specs for token names, not values. The mock should say:

Background: --bg-elev
Border:     --border-soft
Text:       --fg-muted

Not:

Background: #FFFFFF
Border:     #DDDCD8
Text:       #5A5A5A

The hex is incidental — it's what --fg-muted happens to resolve to in light theme. Engineering writes var(--fg-muted), and the value adapts to dark theme automatically. If the mock specifies a literal, engineering has to guess the right token; that's drift.

Per-component handoff checklist

Before pushing a mock to engineering, the designer verifies:

  • Every component instance uses a canonical variant (matched by frame name).
  • Every color is named by token, not by hex.
  • Every spacing value is named by token (--spacing-*), not by px.
  • Every typography value is named by token (--text-*), not by font-size declaration.
  • Any proposed new variant / token has a separate PR open in @matter/components or @matter/tokens.
  • The mock renders correctly at the canonical viewports: 360, 768, 1024, 1280.
  • Dark-theme variant exists in the Figma file (every component should have one).

Per-implementation handoff checklist

Before opening a PR that implements a mock, the engineer verifies:

  • Every component imports from @matter/components or @repo/brand — no app-local clones.
  • Every color reads through var(--token-name) — no raw hex.
  • Every spacing reads through var(--spacing-*) or the spacing token utility.
  • The layout reflows correctly at the canonical breakpoints.
  • The implementation matches the Figma mock at light + dark + forced-colors.
  • bun run --filter design check:drift is green.
  • No new design-system primitives invented at the consumer layer.

Anti-patterns

Do — propose a new component via Governance when nothing fits. The Figma variant + the code variant land together.
Don't — ship a one-off component in apps/app. It will drift; engineering will lose the design intent on the next change.
Do — name Figma frames using the <Component> / <Variant> / <Size> pattern. The mapping falls out automatically.
Don't — name frames freely. "PrimaryButton-Big" doesn't map to anything; engineering has to guess.
Do — annotate the mock with token names. Engineering reads tokens, not hex.
Don't — leave the engineer to guess which token a literal hex maps to. They will guess; it will drift.

When you hit a mismatch

SymptomLikely causeFix
Component looks right but margin is wrongContainer query or Section paddingRead the recipe; the surrounding Section may set vertical rhythm
Color is "off" but the token is rightWrong theme — designer used dark token in light context, or vice versaRe-test at both themes
Variant from Figma doesn't exist in codeDesigner used a Figma-only variantPropose the variant via Governance, or replace with the canonical variant in the mock
Implementation works in light, breaks in darkHard-coded color or hard-coded surfaceReplace literals with var(--token-name)
Forced-colors rendering looks brokenComponent doesn't handle forced-colors fallbackVerify per the accessibility matrix

Reference

On this page