From figma-mcp-skills
Build a pixel-perfect 1:1 replica of any web page in Figma, using the exact colours, fonts, images, and content from the live URL. Use this skill when the user provides a URL and asks to "build this in Figma", "create a Figma design from this page", "replicate this site in Figma", "turn this URL into a Figma design", "clone this page into Figma", or "build [URL] in Figma". Always use this skill when a URL is provided alongside any Figma intent. Do NOT use design system components — replicate the page as-is.
How this skill is triggered — by the user, by Claude, or both
Slash command
/figma-mcp-skills:mainThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill fetches a live URL and builds a pixel-faithful Figma replica using the page's exact colours, fonts, spacing, images, and text content. No design system components are used — the goal is a faithful copy of the source, not an abstraction of it.
This skill fetches a live URL and builds a pixel-faithful Figma replica using the page's exact colours, fonts, spacing, images, and text content. No design system components are used — the goal is a faithful copy of the source, not an abstraction of it.
MANDATORY: Load figma-use before any use_figma call. That skill contains the Plugin API rules (return pattern, color range 0–1, font loading, page context reset, FILL after appendChild) that apply to every script you write.
create_new_file.Follow these steps in order. Do not skip steps.
Use WebFetch to retrieve the URL. Extract the following — be thorough and exact:
Pull exact values from the page's CSS. Look for:
"Inter", "Geist", "Playfair Display"). Note which weight styles are needed (Regular, Medium, SemiBold, Bold, etc.)Record these as a style token table before touching Figma:
Style Tokens
────────────
Colours
Background: #0A0A0A
Surface: #141414
Text primary: #FFFFFF
Text muted: #A1A1AA
Accent: #6366F1
Border: #27272A
Fonts
Heading: "Geist" Bold (700) — sizes: 64px, 48px, 32px
Body: "Geist" Regular (400) — sizes: 16px, 14px
UI: "Geist" Medium (500) — sizes: 14px
Spacing
Section padding: 96px top/bottom, 80px left/right
Component gap: 24px
Element gap: 12px
Radii
Button: 6px
Card: 12px
Map every section of the page in document order with its full copy:
Section Map
───────────
[Nav]
Background: #0A0A0A, border-bottom: 1px solid #27272A
Left: Logo "Acme" (Geist Bold 18px, white)
Centre: links — Product, Pricing, Docs, Blog (Geist Regular 14px, #A1A1AA)
Right: "Sign in" (text link, white), "Get started" (button, accent bg #6366F1, white text, 6px radius, 14px 20px padding)
[Hero]
Background: #0A0A0A
Centred layout, padding: 96px 80px
Badge: "New · v2.0 released" (#27272A bg, #A1A1AA text, 6px radius)
H1: "Ship faster with Acme" (Geist Bold 64px, white)
Subheading: "The platform for teams who move fast." (Geist Regular 20px, #A1A1AA)
CTAs: "Start free" (accent button), "View docs" (outline button, #27272A border)
Gap between badge/h1/sub/ctas: 24px
[Features] 3-column grid, gap 24px
Card bg: #141414, border: 1px #27272A, radius: 12px, padding: 32px
Card 1: icon (purple), "Fast" (Geist SemiBold 20px white), "Deploy in seconds..." (Regular 14px muted)
...
Do not paraphrase or truncate copy. If text is cut off in the HTML, mark it [...].
If no file was provided:
whoami to get the plan keycreate_new_file with the site name (e.g. "acme.com")Then create a new page:
const page = figma.createPage();
page.name = "acme.com/pricing"; // use actual path
await figma.setCurrentPageAsync(page);
return { pageId: page.id };
Load every font family and style extracted in Step 1 before any text operations. If a font is not available in Figma (e.g. a custom or paid typeface), substitute with the closest available match and note it.
// Example — adapt to the actual fonts found on the page
await figma.loadFontAsync({ family: "Geist", style: "Regular" });
await figma.loadFontAsync({ family: "Geist", style: "Medium" });
await figma.loadFontAsync({ family: "Geist", style: "SemiBold" });
await figma.loadFontAsync({ family: "Geist", style: "Bold" });
If a font fails to load, call figma.listAvailableFontsAsync() to find the exact available style name (e.g. "Semi Bold" vs "SemiBold").
One use_figma call. Position it clear of existing content. Return its ID.
let maxX = 0;
for (const child of figma.currentPage.children) {
maxX = Math.max(maxX, child.x + child.width);
}
const wrapper = figma.createFrame();
wrapper.name = "acme.com — /pricing";
wrapper.layoutMode = "VERTICAL";
wrapper.primaryAxisAlignItems = "CENTER";
wrapper.counterAxisAlignItems = "CENTER";
wrapper.itemSpacing = 0;
wrapper.clipsContent = false;
wrapper.fills = [{ type: "SOLID", color: { r: 0.04, g: 0.04, b: 0.04 } }]; // exact page bg colour
wrapper.resize(1440, 100);
wrapper.layoutSizingHorizontal = "FIXED";
wrapper.primaryAxisSizingMode = "AUTO";
wrapper.x = maxX + 200;
wrapper.y = 0;
return { success: true, wrapperId: wrapper.id };
One section per use_figma call. At the start of each script, fetch the wrapper by ID and append directly to it. Never build sections as top-level page children and reparent later — appendChild across calls silently fails.
Use exact values from the style token table — hex colours converted to 0–1 range, exact font families and sizes, exact padding and gap values, exact border radii.
Hex → Figma 0–1 range:
// Helper to use inline
function hex(h) {
const n = parseInt(h.replace('#',''), 16);
return { r: ((n>>16)&255)/255, g: ((n>>8)&255)/255, b: (n&255)/255 };
}
// Usage
frame.fills = [{ type: "SOLID", color: hex("#6366F1") }];
text.fills = [{ type: "SOLID", color: hex("#A1A1AA") }];
frame.strokes = [{ type: "SOLID", color: hex("#27272A") }];
frame.strokeWeight = 1;
frame.strokeAlign = "INSIDE";
await figma.loadFontAsync({ family: "Geist", style: "Bold" });
const heading = figma.createText();
heading.fontName = { family: "Geist", style: "Bold" };
heading.fontSize = 64;
heading.characters = "Ship faster with Acme"; // exact copy from page
heading.fills = [{ type: "SOLID", color: hex("#FFFFFF") }];
// Set line height if needed
heading.lineHeight = { unit: "PIXELS", value: 72 };
For images extracted from the page (hero images, logos, avatars, product screenshots), use figma.createRectangle() as a placeholder with the correct dimensions and a note label. If the image URL is accessible, use figma.createImageAsync(url):
// Placeholder (always works)
const imgPlaceholder = figma.createRectangle();
imgPlaceholder.resize(600, 400);
imgPlaceholder.fills = [{ type: "SOLID", color: { r: 0.15, g: 0.15, b: 0.15 } }];
imgPlaceholder.cornerRadius = 12;
imgPlaceholder.name = "Image: hero screenshot";
// Actual image (when URL is accessible and not auth-gated)
const image = await figma.createImageAsync("https://acme.com/hero.png");
const imageRect = figma.createRectangle();
imageRect.resize(600, 400);
imageRect.fills = [{ type: "IMAGE", scaleMode: "FILL", imageHash: image.hash }];
wrapper.appendChild(section);
section.layoutSizingHorizontal = "FILL"; // MUST be after appendChild
After each section, call get_screenshot on that section's node ID (not just the full page). Check for:
Fix issues with targeted scripts before moving to the next section.
After all sections, call get_screenshot on the full wrapper node. Compare against the source URL. Report:
const nav = figma.createFrame();
nav.name = "Nav";
nav.layoutMode = "HORIZONTAL";
nav.primaryAxisAlignItems = "CENTER";
nav.counterAxisAlignItems = "CENTER";
nav.primaryAxisSizingMode = "FIXED";
nav.resize(1440, 10);
nav.counterAxisSizingMode = "AUTO";
nav.paddingLeft = 80;
nav.paddingRight = 80;
nav.paddingTop = 20;
nav.paddingBottom = 20;
nav.fills = [{ type: "SOLID", color: hex("#0A0A0A") }];
nav.strokes = [{ type: "SOLID", color: hex("#27272A") }];
nav.strokeWeight = 1;
nav.strokeAlign = "INSIDE";
const hero = figma.createFrame();
hero.name = "Hero";
hero.layoutMode = "VERTICAL";
hero.primaryAxisAlignItems = "CENTER";
hero.counterAxisAlignItems = "CENTER";
hero.primaryAxisSizingMode = "AUTO";
hero.counterAxisSizingMode = "FIXED";
hero.resize(1440, 10);
hero.paddingTop = 96;
hero.paddingBottom = 96;
hero.paddingLeft = 80;
hero.paddingRight = 80;
hero.itemSpacing = 24;
hero.fills = [{ type: "SOLID", color: hex("#0A0A0A") }];
const btn = figma.createFrame();
btn.name = "Button / Primary";
btn.layoutMode = "HORIZONTAL";
btn.primaryAxisAlignItems = "CENTER";
btn.counterAxisAlignItems = "CENTER";
btn.primaryAxisSizingMode = "AUTO";
btn.counterAxisSizingMode = "AUTO";
btn.paddingTop = 10;
btn.paddingBottom = 10;
btn.paddingLeft = 20;
btn.paddingRight = 20;
btn.cornerRadius = 6;
btn.fills = [{ type: "SOLID", color: hex("#6366F1") }];
await figma.loadFontAsync({ family: "Geist", style: "Medium" });
const btnLabel = figma.createText();
btnLabel.fontName = { family: "Geist", style: "Medium" };
btnLabel.fontSize = 14;
btnLabel.characters = "Get started";
btnLabel.fills = [{ type: "SOLID", color: hex("#FFFFFF") }];
btn.appendChild(btnLabel);
const card = figma.createFrame();
card.name = "Card";
card.layoutMode = "VERTICAL";
card.primaryAxisSizingMode = "AUTO";
card.counterAxisSizingMode = "FIXED";
card.resize(380, 10);
card.paddingTop = 32;
card.paddingBottom = 32;
card.paddingLeft = 32;
card.paddingRight = 32;
card.itemSpacing = 16;
card.cornerRadius = 12;
card.fills = [{ type: "SOLID", color: hex("#141414") }];
card.strokes = [{ type: "SOLID", color: hex("#27272A") }];
card.strokeWeight = 1;
card.strokeAlign = "INSIDE";
const grid = figma.createFrame();
grid.name = "Grid";
grid.layoutMode = "HORIZONTAL";
grid.layoutWrap = "WRAP";
grid.primaryAxisSizingMode = "FIXED";
grid.resize(1200, 10);
grid.counterAxisSizingMode = "AUTO";
grid.itemSpacing = 24;
grid.counterAxisSpacing = 24;
grid.fills = [];
If a use_figma call fails, stop immediately. Failed scripts are atomic — nothing is written on error. Read the error, fix the script, retry.
| Error | Cause | Fix |
|---|---|---|
| Font not found | Wrong style name | Call figma.listAvailableFontsAsync() to find exact name |
FILL before parent | layoutSizingHorizontal = "FILL" set before appendChild | Move after appendChild |
| Wrapper node null | Wrong page context | Add await figma.setCurrentPageAsync(page) at top of script |
| Image load fails | Auth-gated or CORS-blocked URL | Use placeholder rectangle instead |
| Text truncated | Frame too narrow / fixed height | Set primaryAxisSizingMode = "AUTO" and remove fixed height |
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub markj81/figma-mcp-skills --plugin figma-build-from-url