Skip to content

Commit 1dcee52

Browse files
simeonoffCopilotkdinevdesig9stein
authored
feat: introduce a skill for figma-to-app implementation (#17309)
* feat: introduce a skill for figma-to-app implementation * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(skill/figma-to-app): correct terminology, scaffolding, asset extraction, and spacing guidance - Rename kit references to Indigo.Design UI Kits (Material/Fluent/Bootstrap/Indigo); add kit variant detection and direct designSystem mapping in Phase 1d and design-token-bridge.md - Asset extraction now always uses the Figma REST API (Method A: /v1/files/:key/images for image fills; Method B: /v1/images/:key for node exports); demote figma_get_screenshot and localhost URLs to identification-only use replace with scoped CSS custom property patterns - Rename "Figma UI Kit Name" table columns to "Kit Component Name" throughout figma-component-map.md --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Konstantin Dinev <kdinev@infragistics.com> Co-authored-by: Marin Popov <desig9stein@users.noreply.github.com>
1 parent e2250c2 commit 1dcee52

6 files changed

Lines changed: 2560 additions & 0 deletions

File tree

skills/igniteui-angular-figma-to-app/SKILL.md

Lines changed: 760 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
# Asset Extraction from Figma
2+
3+
> **Part of the [`igniteui-angular-figma-to-app`](../SKILL.md) skill.**
4+
>
5+
> Use this file in Phase 1h to identify and extract image assets from Figma artboards
6+
> before implementation. Read in full before calling any extraction tool.
7+
>
8+
> **Zero-placeholder policy:** every image asset visible in the Figma design must be
9+
> extracted and committed to `src/assets/` before Phase 4 begins. Gradient placeholders
10+
> and empty `<div>` boxes are not acceptable. If the highest-fidelity method is
11+
> unavailable, use the next tier — but always extract something real.
12+
13+
---
14+
15+
## Step 0 — Acquire the Figma File Key (Required for REST API)
16+
17+
The Figma REST API requires a **file key** — the identifier embedded in every Figma
18+
file URL. Without it, you can still extract assets using Tier 2 and Tier 3 methods
19+
below, but the REST API (Tier 1) produces the highest quality output and should always
20+
be the first attempt.
21+
22+
**Ask the user for the file key at the start of Phase 1h:**
23+
24+
> "To extract image assets at the highest quality, I need the Figma file key.
25+
> In the Figma desktop app:
26+
>
27+
> 1. Right-click the file tab at the top → **Copy link** (or go to **File → Share** and copy the URL)
28+
> 2. The URL looks like: `https://www.figma.com/design/ABCDEF1234567890/My-File-Name`
29+
> 3. The file key is the segment after `/design/`: **`ABCDEF1234567890`**
30+
>
31+
> Please share that key (or the full URL). If you cannot access it right now, I will
32+
> proceed with the desktop-app extraction methods and note which assets need to be
33+
> re-exported at higher quality."
34+
35+
**Extracting the key from a URL (if the user pastes it):**
36+
37+
```bash
38+
# URL format: https://www.figma.com/design/<FILE_KEY>/<file-name>?...
39+
# Extract key with sed:
40+
echo "https://www.figma.com/design/ABCDEF1234567890/My-App" \
41+
| sed -E 's|.*/design/([^/]+)/.*|\1|'
42+
# → ABCDEF1234567890
43+
export FILE_KEY="ABCDEF1234567890"
44+
```
45+
46+
**Your Figma personal access token** (same one used for the MCP server) is needed for REST calls. Store it as an environment variable:
47+
48+
```bash
49+
export FIGMA_TOKEN="your-personal-access-token"
50+
```
51+
52+
---
53+
54+
## Two Fundamentally Different Types of Image Assets
55+
56+
Figma stores images in two distinct ways — understanding the difference is essential for choosing the right extraction method:
57+
58+
| Type | What it is | Figma signal | Best extraction method |
59+
| ---------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- | ---------------------------------------- |
60+
| **Image fill** | A photo, texture, or raster image dragged/pasted into Figma. Stored as an IMAGE fill on a RECTANGLE or FRAME node. | Layer has fill of type `IMAGE`; `imageRef` in node data | REST API Method A → original source file |
61+
| **Vector asset** | A logo, icon, or illustration drawn in Figma using vector tools. | Node type `VECTOR`, `BOOLEAN_OPERATION`, or `GROUP` | REST API Method B → clean SVG |
62+
63+
Do **not** confuse these with Ignite UI component instances — those become Angular components, not extracted assets.
64+
65+
---
66+
67+
## Step 1 — Identify Image Nodes
68+
69+
### Naming patterns to look for in `figma_get_metadata`
70+
71+
Scan the XML returned by `figma_get_metadata` for layer names matching:
72+
73+
| Naming pattern | Likely asset type | Action |
74+
| -------------------------------------------------- | ----------------- | ----------------------------------------- |
75+
| `_Image`, `_Photo`, `_Picture`, `image`, `photo` | Raster image fill | Extract as PNG |
76+
| `_Hero`, `_Banner`, `_Cover`, `hero`, `banner` | Large raster fill | Extract as PNG (2× scale) |
77+
| `_Thumbnail`, `_Avatar`, `_Illustration` | Raster image fill | Extract as PNG |
78+
| `_Logo`, `_Brand`, `logo`, `brand` | Vector or raster | If vector: extract as SVG; if raster: PNG |
79+
| `_Icon/` prefix (custom icons not in igx-icon set) | Custom SVG icon | Extract as SVG |
80+
| `_Background`, `_Bg`, `bg`, `background` | Large raster fill | Extract as PNG |
81+
| `_Illustration`, `_Art`, `_Pattern` | Vector or raster | Classify before extracting |
82+
83+
> **Ignore these** — do NOT extract them as image assets:
84+
>
85+
> - Any layer whose name starts with `_Button`, `_Input`, `_Grid`, `_Card`, etc.
86+
> (Indigo.Design UI Kit component instances → implement as IgxXxx components)
87+
> - `igx-icon` glyph nodes (use `<igx-icon>` in Angular instead)
88+
> - Artboard/frame boundaries themselves
89+
90+
### Size heuristic
91+
92+
Large rectangles (width > 200px or height > 200px) at key layout positions (hero area,
93+
sidebar background, card thumbnail slot) are almost always image fills.
94+
95+
Small nodes (< 48×48px) named with icon-like names are usually SVG icons.
96+
97+
### Confirm with design context
98+
99+
For ambiguous nodes, look at the `figma_get_design_context` output for the artboard.
100+
Each image asset appears as either:
101+
102+
- An `<img src="http://localhost:3845/assets/...">` — confirms it is a raster fill; note the node ID
103+
- Inline SVG or an `<img>` with `.svg` extension — confirms it is a vector; note the node ID
104+
105+
Note all localhost image URLs from the design context — these are needed for Tier 2
106+
extraction if the REST API is unavailable.
107+
108+
---
109+
110+
## Step 2 — Extract at the Highest Available Fidelity
111+
112+
Use this **four-tier decision tree**. Start at Tier 1. Move to the next tier only if
113+
the previous one is unavailable for this specific asset.
114+
115+
```
116+
Do you have the FILE_KEY?
117+
├─ YES → Use Tier 1 (REST API). Always the best.
118+
└─ NO → Did figma_get_design_context include a localhost URL for this asset?
119+
├─ YES → Use Tier 2 (download localhost URL to disk).
120+
└─ NO → Can you get a clean node screenshot?
121+
├─ YES → Use Tier 3 (figma_get_screenshot per node).
122+
└─ NO → Use Tier 4 (CSS gradient/color placeholder as last resort,
123+
with a TODO comment to replace later).
124+
```
125+
126+
**After completing Phase 4, if you used Tier 2 or Tier 3 for any asset:**
127+
128+
> Tell the user: "The following assets were extracted at reduced quality because the
129+
> Figma file key was not available: [list]. To replace them with the original
130+
> source files, run the Tier 1 REST API commands in Step 2a once you have the file key."
131+
132+
---
133+
134+
### Tier 1 — REST API (Highest Fidelity)
135+
136+
**Requires:** `FILE_KEY` and `FIGMA_TOKEN`.
137+
138+
#### Method A — Original Image Fills
139+
140+
Use this to download **photos, textures, and raster images** that were uploaded to
141+
Figma (identified by `imageRef` in node fill data). Returns the original source file
142+
at its native resolution — never a re-render.
143+
144+
```bash
145+
# Step A.1 — Get all image fill URLs in the file
146+
curl -s \
147+
-H "X-Figma-Token: $FIGMA_TOKEN" \
148+
"https://api.figma.com/v1/files/$FILE_KEY/images" \
149+
-o /tmp/figma_image_fills.json
150+
151+
# Response: { "images": { "<imageRef>": "<cdn-url>", ... } }
152+
153+
# Step A.2 — Get node fill data to match imageRef → node
154+
curl -s \
155+
-H "X-Figma-Token: $FIGMA_TOKEN" \
156+
"https://api.figma.com/v1/files/$FILE_KEY/nodes?ids=$NODE_IDS" \
157+
-o /tmp/figma_nodes.json
158+
# In the response, look for: "fills": [{ "type": "IMAGE", "imageRef": "abc123" }]
159+
# Match "abc123" → URL from figma_image_fills.json
160+
161+
# Step A.3 — Download to assets folder
162+
mkdir -p src/assets/images src/assets/icons
163+
IMAGE_URL=$(jq -r '.images["<imageRef>"]' /tmp/figma_image_fills.json)
164+
curl -sL "$IMAGE_URL" -o src/assets/images/hero-background.jpg
165+
```
166+
167+
> **URL expiry:** image fill URLs expire in **14 days**. Download during the session.
168+
169+
#### Method B — Node Export (SVG, PNG, JPG)
170+
171+
Use this to export **any node** as SVG, PNG, or JPG. Best for logos, custom icons,
172+
vector illustrations, and raster compositions.
173+
174+
```bash
175+
mkdir -p src/assets/images src/assets/icons
176+
177+
# SVG export (vectors — logos, icons, illustrations)
178+
curl -s \
179+
-H "X-Figma-Token: $FIGMA_TOKEN" \
180+
"https://api.figma.com/v1/images/$FILE_KEY?ids=$NODE_IDS&format=svg&svg_outline_text=false&contents_only=true" \
181+
-o /tmp/figma_svg_export.json
182+
183+
jq -r '.images | to_entries[] | "\(.key)\t\(.value)"' /tmp/figma_svg_export.json | \
184+
while IFS=$'\t' read -r nodeId url; do
185+
filename="$(echo "$nodeId" | tr ':' '-').svg"
186+
curl -sL "$url" -o "src/assets/icons/$filename"
187+
echo "Downloaded $nodeId → src/assets/icons/$filename"
188+
done
189+
190+
# PNG export at 2× (raster compositions, hero banners)
191+
curl -s \
192+
-H "X-Figma-Token: $FIGMA_TOKEN" \
193+
"https://api.figma.com/v1/images/$FILE_KEY?ids=$NODE_IDS&format=png&scale=2&contents_only=true" \
194+
-o /tmp/figma_png_export.json
195+
196+
jq -r '.images | to_entries[] | "\(.key)\t\(.value)"' /tmp/figma_png_export.json | \
197+
while IFS=$'\t' read -r nodeId url; do
198+
filename="$(echo "$nodeId" | tr ':' '-').png"
199+
curl -sL "$url" -o "src/assets/images/$filename"
200+
done
201+
```
202+
203+
**Key export parameters:**
204+
205+
| Parameter | Value | When to use |
206+
| ------------------ | ------- | ---------------------------------------------------- |
207+
| `format` | `svg` | Logos, icons, vector illustrations |
208+
| `format` | `png` | Hero backgrounds, thumbnails — always pair `scale=2` |
209+
| `format` | `jpg` | Photos without transparency — use `scale=2` |
210+
| `scale` | `2` | **Always for PNG/JPG** — retina-quality output |
211+
| `svg_outline_text` | `false` | Keep text as `<text>` elements (smaller, accessible) |
212+
| `contents_only` | `true` | Render the node in isolation (default) |
213+
214+
> **Export URL expiry:** export URLs expire in **30 days**. Download immediately.
215+
216+
---
217+
218+
### Tier 2 — Localhost URL Download (Desktop MCP Fallback)
219+
220+
**Use when:** the file key is unavailable and `figma_get_design_context` returned
221+
localhost URLs (e.g. `http://localhost:3845/assets/abc123.png`).
222+
223+
These URLs are served by the Figma desktop app's in-memory renderer and are accessible
224+
via `curl` during the active Figma session. They are **not** the original source file
225+
(they are a renderer output), but they are substantially better than placeholders and
226+
can be committed to the repository.
227+
228+
```bash
229+
mkdir -p src/assets/images src/assets/icons
230+
231+
# Extract localhost URLs from a design context output and download them
232+
# Replace <localhost-url> with the actual URL found in figma_get_design_context output
233+
curl -sL "http://localhost:3845/assets/<hash>.png" -o src/assets/images/hero-background.png
234+
curl -sL "http://localhost:3845/assets/<hash>.svg" -o src/assets/icons/logo.svg
235+
```
236+
237+
**Finding localhost URLs in design context output:**
238+
239+
In the React+Tailwind code returned by `figma_get_design_context`, look for:
240+
241+
- `const imgXxx = "http://localhost:3845/assets/<hash>.<ext>";` at the top of the output
242+
- `<img src={imgXxx} />` or `background-image` references inline
243+
244+
Each `const` at the top is an image asset. Note its variable name, the URL, and which
245+
Figma node it belongs to (from context around the `<img>` tag).
246+
247+
**Limitations of Tier 2 assets:**
248+
249+
- Raster renders — vectors become PNGs, not SVGs
250+
- Renderer resolution (typically 2×) — adequate for most uses
251+
- Expire when the Figma desktop app closes — **must be downloaded before closing Figma**
252+
- Must be renamed from `<hash>.png` to descriptive names before committing
253+
254+
**Commit as-is** — they are real assets. Add a comment in the asset manifest:
255+
256+
```typescript
257+
// TODO: Replace with Tier 1 REST API export once FILE_KEY is available
258+
heroBg: 'assets/images/hero-background.png', // extracted from Figma session
259+
```
260+
261+
---
262+
263+
### Tier 3 — `figma_get_screenshot` per Node
264+
265+
**Use when:** no file key AND no localhost URLs were produced for a specific node.
266+
267+
`figma_get_screenshot` renders any currently-selected Figma node as a PNG screenshot.
268+
Ask the user to select each image node in Figma, then call the tool.
269+
270+
```
271+
// 1. Ask: "In Figma, please click the [Hero Background] layer to select it."
272+
// 2. After confirmation:
273+
figma_get_screenshot({})
274+
// 3. The returned image is a 1× screen-capture PNG. Save it to src/assets/images/.
275+
```
276+
277+
**Limitations:**
278+
279+
- 1× resolution — suitable as a placeholder but blurry on retina screens
280+
- Rasterizes all vectors — logos become PNGs
281+
- May include surrounding canvas elements
282+
283+
Label these assets clearly in the manifest:
284+
285+
```typescript
286+
// TODO: Re-export at 2× via Tier 1 or Tier 2 — current version is 1× screen capture
287+
heroBg: 'assets/images/hero-background.png',
288+
```
289+
290+
---
291+
292+
### Tier 4 — CSS Fallback (Last Resort Only)
293+
294+
**Use only when** an asset is confirmed to be a pure color fill or a gradient — not when
295+
an image exists in Figma but extraction failed. Never use Tier 4 because extraction
296+
feels difficult.
297+
298+
```scss
299+
// Only acceptable when the Figma layer is genuinely a gradient, not a photo
300+
.hero-banner {
301+
// TODO: Replace with real asset — extraction blocked (no file key, no session URL)
302+
background: linear-gradient(135deg, #0d1b3e 0%, #1a0533 100%);
303+
}
304+
```
305+
306+
If you use Tier 4, add the `TODO` comment and include it in the post-session handoff
307+
notes to the user.
308+
309+
---
310+
311+
## Step 3 — Build an Asset Manifest
312+
313+
After extraction, create a manifest so the implementation phase uses consistent paths:
314+
315+
```typescript
316+
// src/app/<page>/_assets.ts — generated during Phase 1h
317+
318+
export const PAGE_ASSETS = {
319+
// Figma node 123:456 — "Hero/Background" layer
320+
// Tier 1: REST API PNG export at 2×
321+
heroBg: 'assets/images/hero-background.jpg',
322+
323+
// Figma node 789:012 — "_Logo/Main" layer
324+
// Tier 1: REST API SVG export
325+
logo: 'assets/icons/logo.svg',
326+
327+
// Figma node 345:678 — "Card/Thumbnail" layer
328+
// Tier 2: localhost URL download (TODO: re-export via REST API)
329+
cardThumbnail: 'assets/images/card-thumbnail.png',
330+
} as const;
331+
```
332+
333+
Name files by layer purpose, not by node ID.
334+
335+
---
336+
337+
## Step 4 — Angular Usage Patterns
338+
339+
### Static images via `NgOptimizedImage`
340+
341+
```html
342+
<!-- Preferred: NgOptimizedImage for LCP/CLS optimization -->
343+
<img ngSrc="assets/images/hero-background.jpg" width="1440" height="600" alt="Dashboard hero background" priority />
344+
```
345+
346+
Import `NgOptimizedImage` in the component's `imports` array. Note: `NgOptimizedImage`
347+
does **not** work for inline base64 images.
348+
349+
### Background images via SCSS
350+
351+
```scss
352+
.dashboard-hero {
353+
background-image: url('/assets/images/hero-background.jpg');
354+
background-size: cover;
355+
background-position: center;
356+
}
357+
```
358+
359+
### Inline SVG logos
360+
361+
```html
362+
<img src="assets/icons/logo.svg" alt="Company logo" width="120" height="40" />
363+
```
364+
365+
### Igx-icon for kit icons (do NOT extract as assets)
366+
367+
Standard Material icons and `imx-icons` (Material Icons Extended) are registered at
368+
runtime — never extract them as image files:
369+
370+
```html
371+
<igx-icon>settings</igx-icon> <igx-icon family="imx-icons" name="credit-cards"></igx-icon>
372+
```
373+
374+
See `figma-component-map.md § Material Icons Extended` for setup.
375+
376+
---
377+
378+
## Pitfalls
379+
380+
| Pitfall | Consequence | Fix |
381+
| ------------------------------------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
382+
| Skipping asset extraction entirely (gradient placeholders) | Implementation looks nothing like the design; Phase 5 fails | Always use at least Tier 2 or Tier 3 — never skip |
383+
| Not asking for the file key before starting extraction | Defaults to Tier 2/3 when Tier 1 was actually possible | Ask for the file key at the start of Phase 1h (see Step 0) |
384+
| Using localhost URLs without downloading them in the session | URLs expire when Figma closes; assets become broken | Download with `curl` immediately; commit the files |
385+
| Naming assets by node ID ("node-123-456.png") | Unmaintainable; breaks if Figma is reorganized | Name by layer purpose: `hero-background.jpg`, `company-logo.svg` |
386+
| Using `figma_get_screenshot` for SVG logos | Logo is rasterized to PNG; loses vector scalability | Use Tier 1 Method B with `format=svg` instead; fall back to Tier 2 only if unavailable |
387+
| Exporting PNG at `scale=1` | Blurry on HiDPI/retina screens | Always use `scale=2` |
388+
| Storing Figma CDN export URLs in source code | URLs expire in 14–30 days; breaks production | Only store local `src/assets/...` paths in code |
389+
| Downloading SVG with `svg_outline_text=true` (default) | All text converted to paths; larger file; no accessibility | Set `svg_outline_text=false` |
390+
| Extracting Ignite UI component instances as images | Produces a static screenshot instead of an interactive component | Identify layer names from `figma-component-map.md`; those are components, not assets |
391+
| Extracting background colors or gradients as images | Inflates bundle size, breaks theming | Colors → CSS custom properties or Ignite UI theming tokens |
392+
| Not creating asset directories before running curl | Writes fail silently or to the wrong path | Always run `mkdir -p src/assets/images src/assets/icons` first |
393+
| Large SVGs from complex illustrations | Slow render, large bundle | Consider PNG for illustrations > 50KB as SVG; SVG is best for logos and icons |

0 commit comments

Comments
 (0)