11# Figma MCP
22
3- Mandatory reading whenever you are implementing UI from Figma through the Figma MCP (` get_design_context ` ,
4- ` get_metadata ` , ` get_screenshot ` , ` use_figma ` ) or when the user provides a ` figma.com/... ` URL. This file only
5- covers the translation layer — the rest of the Mistica docs (` patterns.md ` , ` components.md ` , ` layout.md ` ,
6- ` design-tokens.md ` ) still apply.
3+ Mandatory reading whenever you are implementing UI from Figma through the Figma MCP or when the user provides
4+ a ` figma.com/... ` URL. This file only covers the translation layer — the rest of the Mistica docs
5+ (` patterns.md ` , ` components.md ` , ` layout.md ` , ` design-tokens.md ` ) still apply.
76
87## Prime directive: read the DOM verbatim
98
109The Figma MCP response gives you two things:
1110
1211- a ** screenshot** that shows what the design should look like
13- - a ** DOM** (React + Tailwind) that shows what the designer specified
12+ - a ** DOM** that shows what the designer specified
1413
1514Use the DOM as the source of truth for every numeric and structural decision. The screenshot only validates
1615that your implementation matches the designer's intent.
1716
1817** If you cannot point at a line in the DOM to justify a value, do not write that value.** Do not pick "nearby
19- nicer" numbers. Do not default to Mistica's 16 / 24 / 32 vertical rhythm when the DOM is explicit. If Figma
20- says ` 72 ` , use ` 72 ` ; if Figma says ` justify-between ` , use ` space="between" ` — not an invented fixed gap.
18+ nicer" numbers.
2119
2220## Mapping Figma flex to Mistica layout primitives
2321
24- | Figma / Tailwind | Mistica |
22+ | Figma | Mistica |
2523| --------------------------------------- | --------------------------------------------------- |
2624| ` flex gap-[Npx] ` (vertical, ` flex-col ` ) | ` Stack space={N} ` |
2725| ` flex gap-[Npx] ` (horizontal) | ` Inline space={N} ` |
@@ -37,44 +35,6 @@ says `72`, use `72`; if Figma says `justify-between`, use `space="between"` —
3735Each spacing primitive has its own allowed scale. Figma values outside the scale must be rounded to the
3836nearest allowed value and noted — never silently apply arbitrary CSS.
3937
40- | Primitive | Allowed values |
41- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
42- | ` Box ` ` padding* ` | ` 0 \| 2 \| 4 \| 8 \| 12 \| 16 \| 20 \| 24 \| 32 \| 40 \| 48 \| 56 \| 64 \| 72 \| 80 ` |
43- | ` Stack ` ` space ` | ` 0 \| 2 \| 4 \| 8 \| 12 \| 16 \| 24 \| 32 \| 40 \| 48 \| 56 \| 64 \| 72 \| 80 ` + ` "between" \| "around" \| "evenly" ` |
44- | ` Inline ` ` space ` | ` -16 \| -12 \| -8 \| -4 \| -2 \| 0 \| 2 \| 4 \| 8 \| 12 \| 16 \| 24 \| 32 \| 40 \| 48 \| 56 \| 64 ` + ` "between" \| "around" \| "evenly" ` |
45-
46- ` Inline ` notably allows negative values and caps at 64; ` Stack ` starts at 0 and caps at 80; ` Box ` includes
47- ` 20 ` which the others don't. If Figma says ` gap-[10px] ` , it doesn't fit any of these — round to ` 8 ` or ` 12 `
48- and flag it, don't invent a CSS override.
49-
50- ## The single-child gap trap
51-
52- A ` flex gap-[Npx] ` class applied to a wrapper that has ** only one child** renders no space — gap is between
53- siblings, not around a solo child. Figma's MCP output often stacks wrappers this way:
54-
55- ``` tsx
56- <div className = " flex gap-[72px] ..." >
57- <div className = " flex ..." >
58- { ' ' }
59- { /* single child */ }
60- <FirstColumn />
61- </div >
62- <div className = " flex gap-[48px] ..." >
63- { ' ' }
64- { /* single child — the 48 never renders */ }
65- <SecondColumn />
66- </div >
67- <div className = " flex gap-[48px] ..." >
68- { ' ' }
69- { /* single child — the 48 never renders */ }
70- <ThirdColumn />
71- </div >
72- </div >
73- ```
74-
75- The real spacing here is ` 72px ` (from the outer wrapper). The ` 48px ` on each inner wrapper is dead CSS. When
76- you see this pattern, use the parent's gap and ignore the child wrappers' gaps.
77-
7838## Don't snap Figma values to Mistica's rhythm
7939
8040Mistica's 16 / 24 / 32 vertical-rhythm guidance in ` patterns.md ` and ` layout.md ` is for ** greenfield
@@ -101,9 +61,8 @@ Ignore per-node `font-[family-name:var(--fontfamily/fontfamily,'Movistar_Sans:Me
10161` GlobalStyles ` — the active skin's font (see ` fonts.md ` ) is the source of truth. Per-node font families in the
10262MCP output are leaked style from the Figma file, not designer intent.
10363
104- Font weight is handled by the Mistica text components (` Text1 ` -` Text4 ` accept ` light ` / ` regular ` / ` medium ` /
105- ` bold ` ; ` Text5 ` -` Text10 ` and ` Title1 ` -` Title4 ` have a fixed weight per skin). Map Figma's ` font-weight/text5 `
106- to the matching component (e.g. ` Text5 ` ), not to a CSS ` font-weight ` .
64+ Font weight is handled by the Mistica text components (` Text1 ` -` Text10 ` and ` Title1 ` -` Title4 ` ). Map Figma's
65+ ` font-weight/text5 ` to the matching component (e.g. ` Text5 ` ), not to a CSS ` font-weight ` .
10766
10867## ` CodeConnectSnippet ` wrappers: gather before you choose
10968
@@ -138,30 +97,6 @@ child slots that correspond to `extra` / `slot` / `headline`, sibling buttons, e
13897Use ` get_metadata ` on the same node when you also need to understand which children are component instances
13998vs. raw nodes.
14099
141- The usual composites: ` Hero ` , ` CoverHero ` , ` Header ` , ` CoverCard ` , ` MediaCard ` , ` DataCard ` , ` NakedCard ` ,
142- ` PosterCard ` , ` DisplayMediaCard ` , ` Callout ` , ` EmptyState ` , ` EmptyStateCard ` , ` Row ` , ` BoxedRow ` , anything with
143- a slot. Pure atoms (` IconTruckRegular ` and similar, plain ` ButtonPrimary ` /` ButtonSecondary ` /` ButtonLink ` ) have
144- a single content slot — the snippet plus the screenshot is usually enough, but if anything looks off, drill in
145- anyway.
146-
147- ### How snippets go wrong
148-
149- Some failure modes to keep in mind — not as a checklist of things to detect, but as reasons to gather the
150- underlying data rather than read values off the snippet:
151-
152- - ** Stub content** — ` title="Title" ` , ` description="Description" ` , ` imageSrc="https://example.com/image.jpg" ` ,
153- ` aspectRatio="16:9" ` on non-landscape media.
154- - ** Values that happen to match the schema but don't match the design** — ` variant="default" ` on a card that
155- actually renders inverse; ` type="info" ` where the real node resolves to ` --tagbackgroundpromo ` ;
156- ` size="default" ` on a snap-size card. These pass type checks silently.
157- - ** Mis-mapped content slots** — a price block under a title looks like it belongs in ` description ` , but the
158- real node lives in a separate content slot that maps to ` extra ` . The snippet may say ` description="..." ` ,
159- may omit it, may stub it — only the real DOM tells you the correct prop.
160- - ** Stale prop names after an API change** — ` backgroundImage ` on ` CoverCard ` when the current API is
161- ` imageSrc ` ; ` isInverse ` where ` variant ` is now preferred.
162- - ** Noisy artifacts** — ` 🔄ReplaceSlot="5267:4885" ` , ` asset="ERROR" ` , ` footer="false" ` (string instead of
163- boolean), conflicting component imports at the top of the output.
164-
165100### Rule of thumb
166101
167102Every prop value you write — text, enum, aspect ratio, boolean — should be something you picked after reading
@@ -171,14 +106,11 @@ drilled-in DOM justifies the value, gather more before committing it.
171106## Assets: always download, store, and serve locally
172107
173108Figma MCP asset URLs (` https://www.figma.com/api/mcp/asset/<uuid> ` ) are valid for only ~ 7 days. Do ** not**
174- inline them anywhere — not in committed code, not in dev-only code, not as "temporary" placeholders. Every
175- time you would paste a Figma MCP URL, download the asset first, save it into the project (e.g.
176- ` public/images/... ` or ` src/assets/... ` ), and reference the local path.
109+ inline them anywhere. Download the asset first, save it into the project (e.g. ` public/images/... ` or
110+ ` src/assets/... ` ), and reference the local path.
177111
178112Do ** not** substitute unrelated stock photos (Unsplash, Picsum, Lorem Picsum, etc.) for the designer's assets.
179- The real images are part of the design — an Xbox controller in the Figma means Xbox, not a random
180- phone-on-a-table from a stock library. Substituting unrelated images is the same failure class as picking an
181- unrelated spacing value.
113+ The real images are part of the design.
182114
183115The right workflow for every image in a Figma design:
184116
@@ -191,33 +123,14 @@ The right workflow for every image in a Figma design:
1911234 . If you cannot resolve an asset — the URL 404s, the node has no fill, the design legitimately has no image
192124 there — say so explicitly and ask, rather than inventing a stock replacement.
193125
194- The only acceptable exception is when the Figma file itself uses a stub URL (` https://example.com/image.jpg ` ),
195- in which case drilling in confirmed there is no real asset, and a placeholder is appropriate — but it should
196- still be a committed asset in the project (e.g. a branded silhouette or a neutral grey rectangle), not a live
197- external URL.
198-
199126## Verification checklist
200127
201- Before closing out a section, be able to justify every decision against the DOM:
202-
203- - [ ] For every composite component on the page (card, hero, header, callout, row…), you fetched the node with
204- ` disableCodeConnect: true ` before writing props — regardless of whether the stub looked filled in or
205- not.
206- - [ ] Every text node in the screenshot maps to a specific Mistica prop that you can point at in the real
207- (non-CodeConnect) DOM. The price line could be ` description ` , ` extra ` , or a custom slot; only the
208- drilled-in DOM tells you which.
209- - [ ] Every non-text prop (` aspectRatio ` , ` size ` , ` variant ` , image URL, boolean flags) traces to an attribute
210- you read from the real DOM, not from the CodeConnect stub. These match the schema even when wrong and
211- typechecks won't catch them.
212- - [ ] Every ` space={N} ` / ` padding={N} ` / ` gap={N} ` traces to a ` gap-[Npx] ` / ` p-[Npx] ` class on a wrapper
213- that actually renders it (not a single-child wrapper), or to a nearest-scale round with a one-line
214- comment explaining the original value.
215- - [ ] Every ` space="between" | "around" | "evenly" ` traces to the matching ` justify-* ` class.
216- - [ ] Every color uses ` skinVars.colors.* ` , not a hex or ` var(--...) ` literal.
217- - [ ] Every font decision comes from the global ` GlobalStyles ` + the skin's defaults, not from a per-node
218- class.
219- - [ ] Every mapped component uses current Mistica props, not the literal attribute list from the
220- ` CodeConnectSnippet ` .
128+ Before closing out a section always:
129+
130+ - [ ] Double check every native html element or style attribute to see if there is a Mistica alternative. If
131+ so, always use Mistica.
132+ - [ ] Double check every composite component on the page was fetched with: ` disableCodeConnect: true ` before
133+ writing props — regardless of whether the stub looked filled in or not.
221134
222135If you can't check an item off against the DOM, re-read the DOM (with ` disableCodeConnect: true ` if the node
223136is CodeConnect-mapped) before committing the value.
0 commit comments