203 lines
8.1 KiB
Markdown
203 lines
8.1 KiB
Markdown
---
|
||
title: "Figma Code Connect — Skill Workflow (.figma.ts templates)"
|
||
aliases: [figma-code-connect-skill, figma-ts-templates, code-connect-workflow]
|
||
tags: [figma, code-connect, mcp, skill, typescript, design-to-code]
|
||
sources: [raw/Skill Code Connect Developer Docs.md]
|
||
created: 2026-05-15
|
||
updated: 2026-05-15
|
||
---
|
||
|
||
# Figma Code Connect — Skill Workflow
|
||
|
||
Step-by-step process for creating `.figma.ts` template files that map Figma components to code snippets using the Figma MCP server skill.
|
||
|
||
> Covers **template files only** (`.figma.ts` using MCP tools). Parser-based files (`.figma.tsx` with `figma.connect()` via CLI) are a separate approach.
|
||
|
||
## Prerequisites
|
||
|
||
- Figma MCP tools available (e.g. `get_code_connect_suggestions`) — verify before starting
|
||
- Component published to a Figma team library (unpublished = stop)
|
||
- Organization or Enterprise plan (not Free/Pro)
|
||
- Figma URL must include `node-id` query param
|
||
- Add `@figma/code-connect/figma-types` to `tsconfig.json`:
|
||
```json
|
||
{ "compilerOptions": { "types": ["@figma/code-connect/figma-types"] } }
|
||
```
|
||
|
||
## 6-Step Workflow
|
||
|
||
### Step 1 — Parse the Figma URL
|
||
|
||
Extract `fileKey` and `nodeId`:
|
||
|
||
| URL format | fileKey | nodeId |
|
||
|-----------|---------|--------|
|
||
| `figma.com/design/:fileKey/:name?node-id=X-Y` | `:fileKey` | `X-Y` → `X:Y` |
|
||
| `figma.com/file/:fileKey/:name?node-id=X-Y` | `:fileKey` | `X-Y` → `X:Y` |
|
||
| Branch URL with `:branchKey` | use `:branchKey` | from `node-id` param |
|
||
|
||
**Always convert hyphens to colons in nodeId**: `1234-5678` → `1234:5678`
|
||
|
||
### Step 2 — Discover Unmapped Components
|
||
|
||
Call `get_code_connect_suggestions` with `fileKey`, `nodeId`, `excludeMappingPrompt: true`.
|
||
|
||
- **"No published components found"** → tell user to publish first, stop
|
||
- **"All already connected"** → inform user, stop
|
||
- **Normal response** → extract `mainComponentNodeId` per component; use these (not the original URL node) for all subsequent steps. Repeat Steps 3–6 for each component.
|
||
|
||
### Step 3 — Fetch Component Properties
|
||
|
||
Call `get_context_for_code_connect` with `fileKey`, resolved `nodeId`, `clientFrameworks`, `clientLanguages`.
|
||
|
||
Property types returned:
|
||
|
||
| Type | Description |
|
||
|------|-------------|
|
||
| TEXT | Text content (labels, placeholders) |
|
||
| BOOLEAN | Toggle (show/hide, disabled) |
|
||
| VARIANT | Enum options (size, state) |
|
||
| INSTANCE_SWAP | Swappable nested instance (icon) |
|
||
| SLOT | Freeform content region |
|
||
|
||
### Step 4 — Identify the Code Component
|
||
|
||
1. Check `figma.config.json` for `paths`/`importPaths`
|
||
2. Search codebase in `src/components/`, `components/`, `lib/ui/`, `app/components/`
|
||
3. Compare props interface vs Figma properties from Step 3
|
||
4. **Confirm with user** before writing the template
|
||
|
||
### Step 5 — Create the Template File
|
||
|
||
**File location:** alongside existing `.figma.ts`/`.figma.tsx` files, named `ComponentName.figma.ts`
|
||
|
||
#### Template structure
|
||
|
||
```ts
|
||
// url=https://www.figma.com/file/{fileKey}/{fileName}?node-id={nodeId}
|
||
// source={path to code component}
|
||
// component={component name}
|
||
import figma from 'figma'
|
||
const instance = figma.selectedInstance
|
||
|
||
// property extractions...
|
||
|
||
export default {
|
||
example: figma.code`<Component ... />`,
|
||
imports: ['import { Component } from "..."'],
|
||
id: 'component-name',
|
||
metadata: { nestable: true, props: {} }
|
||
}
|
||
```
|
||
|
||
#### Property mapping methods
|
||
|
||
| Figma Type | Method | Notes |
|
||
|-----------|--------|-------|
|
||
| TEXT | `instance.getString('Name')` | Returns string |
|
||
| BOOLEAN | `instance.getBoolean('Name', { true: ..., false: ... })` | Mapping optional |
|
||
| VARIANT | `instance.getEnum('Name', { 'FigmaVal': 'codeVal' })` | Must map ALL values |
|
||
| INSTANCE_SWAP | `instance.getInstanceSwap('Name')` | Returns `InstanceHandle \| null` |
|
||
| SLOT | `instance.getSlot('Name')` | Returns `ResultSection[] \| undefined` |
|
||
| Child layer | `instance.findInstance('LayerName')` | No component property |
|
||
| Text layer | `instance.findText('LayerName').textContent` | Named text layer |
|
||
|
||
#### VARIANT exhaustive mapping (critical)
|
||
|
||
Every value from `get_context_for_code_connect` **must** appear in `getEnum`. Unmapped value → silent `undefined`:
|
||
|
||
```ts
|
||
// Correct — all 4 values mapped
|
||
const status = instance.getEnum('Status', {
|
||
'Success': 'success',
|
||
'Error': 'error',
|
||
'Warning': 'warning',
|
||
'Info': 'info',
|
||
})
|
||
```
|
||
|
||
#### Interpolation rules
|
||
|
||
| Value type | Wrapping |
|
||
|-----------|----------|
|
||
| String (`getString`, `getEnum`, `textContent`) | Quotes: `variant="${variant}"` |
|
||
| Instance snippet (`executeTemplate().example`) | Braces: `icon={${iconCode}}` |
|
||
| Slot sections (`getSlot()`) | Directly in template: `` `<X>${content}</X>` `` |
|
||
| Boolean bare prop | Conditional: `${disabled ? 'disabled' : ''}` |
|
||
|
||
#### Instance swap pattern
|
||
|
||
```ts
|
||
const icon = instance.getInstanceSwap('Icon')
|
||
let iconCode
|
||
if (icon && icon.type === 'INSTANCE') { // type check required — findInstance returns ErrorHandle on failure
|
||
iconCode = icon.executeTemplate().example
|
||
}
|
||
```
|
||
|
||
#### SelectorOptions for `findInstance`/`findText`
|
||
|
||
```ts
|
||
// Target inside a nested instance (stop at boundary by default)
|
||
instance.findInstance('child', { traverseInstances: true })
|
||
|
||
// Disambiguate duplicate layer names
|
||
instance.findInstance('child', { traverseInstances: true, path: ['ParentLayer'] })
|
||
```
|
||
|
||
### Step 6 — Validate
|
||
|
||
Read back the file and check:
|
||
- Every Figma property from Step 3 is covered
|
||
- All emitted attributes exist in the code component's `Props` interface (never invent props)
|
||
- No hardcoded children — INSTANCE_SWAP and slots use dynamic APIs
|
||
- INSTANCE_SWAP uses `getInstanceSwap()`, not `getSlot()`; SLOT uses `getSlot()`, not `getInstanceSwap()`
|
||
- `type === 'INSTANCE'` check present before every `executeTemplate()` call
|
||
|
||
## Key Takeaways
|
||
|
||
- **6 steps**: parse URL → discover unmapped → fetch properties → identify code component → write template → validate
|
||
- **VARIANT coverage is mandatory** — every enum value must be in the mapping; missing = silent `undefined`
|
||
- **Never string-concatenate `ResultSection[]`** — interpolate inside `` figma.code`...` `` tagged templates
|
||
- **`hasCodeConnect()` guard is wrong** — always call `executeTemplate()` directly after `type === 'INSTANCE'` check
|
||
- **`getSlot()` ≠ `getInstanceSwap()`** — SLOT type uses `getSlot()`, INSTANCE_SWAP uses `getInstanceSwap()`; they are not interchangeable
|
||
- **Never hardcode child content** — always resolve dynamically via `executeTemplate()`, omit if no Code Connect exists
|
||
- **`findInstance()` returns ErrorHandle (truthy) on failure**, not null — always add `type === 'INSTANCE'` guard
|
||
- **Confirm code component match with user** before writing the template (Step 4)
|
||
|
||
## Quick API Reference
|
||
|
||
### `instance.*` methods
|
||
|
||
| Method | Returns |
|
||
|--------|---------|
|
||
| `getString(prop)` | `string` |
|
||
| `getBoolean(prop, mapping?)` | `boolean \| any` |
|
||
| `getEnum(prop, mapping)` | `any` |
|
||
| `getInstanceSwap(prop)` | `InstanceHandle \| null` |
|
||
| `getSlot(prop)` | `ResultSection[] \| undefined` |
|
||
| `findInstance(name, opts?)` | `InstanceHandle \| ErrorHandle` |
|
||
| `findText(name, opts?)` | `TextHandle \| ErrorHandle` |
|
||
| `findConnectedInstance(id, opts?)` | `InstanceHandle \| ErrorHandle` |
|
||
| `findConnectedInstances(fn, opts?)` | `InstanceHandle[]` |
|
||
| `findLayers(fn, opts?)` | `(InstanceHandle \| TextHandle)[]` |
|
||
|
||
### `InstanceHandle` methods
|
||
|
||
| Method | Returns |
|
||
|--------|---------|
|
||
| `executeTemplate()` | `{ example: ResultSection[], metadata: Metadata }` |
|
||
| `hasCodeConnect()` | `boolean` (avoid as a guard — see pitfalls) |
|
||
| `codeConnectId()` | `string \| null` |
|
||
|
||
## Related Articles
|
||
|
||
- [[wiki/claude-code/figma-mcp-code-connect|figma-mcp-code-connect]] — higher-level overview: CodeConnectSnippet wrapper, CLI vs MCP mappings
|
||
- [[wiki/claude-code/figma-mcp-skills|figma-mcp-skills]] — all 8 Figma MCP skills including `figma-code-connect-components`
|
||
- [[wiki/claude-code/figma-mcp-setup|figma-mcp-setup]] — enable Figma MCP server in Claude Code
|
||
- [[wiki/claude-code/figma-skill-build-screens|figma-skill-build-screens]] — building/updating Figma screens from design system
|
||
- [[wiki/claude-code/figma-mcp-guide|figma-mcp-guide]] — Figma MCP server overview and capabilities
|
||
|
||
## Sources
|
||
|
||
- `raw/Skill Code Connect Developer Docs.md` — Figma Developer Docs, Code Connect skill reference
|