From figma-friend
Clones live webpages into active Figma file: measures layout, colors, fonts, text positions via Chrome DevTools, builds frames/tables with Figma Plugin API.
npx claudepluginhub markacianfrani/claude-code-figma --plugin figma-friendThis skill is limited to using the following tools:
Clone the page at $ARGUMENTS into the active Figma file using the Figma Plugin API.
Implements pixel-perfect UIs from Figma designs, mockups, or specs by extracting exact dimensions, colors, typography, shadows, and assets for zero-deviation code reproduction.
Translates Figma designs into production-ready UI code with 1:1 visual fidelity using MCP server. Use for Figma node URLs, selections, or 'implement design' requests.
Share bugs, ideas, or general feedback.
Clone the page at $ARGUMENTS into the active Figma file using the Figma Plugin API.
You have access to Chrome DevTools Protocol MCP tools (chrome-devtools_*).
Use chrome-devtools_list_pages to find tabs, chrome-devtools_select_page to switch between them, chrome-devtools_evaluate_script to run JS in either the live page or the Figma tab, and chrome-devtools_take_screenshot for captures.
Follow this exact workflow:
Before writing any Figma code, select the live page tab with chrome-devtools_select_page and extract every measurement using chrome-devtools_evaluate_script with getBoundingClientRect():
window.innerWidth, window.innerHeight, devicePixelRatiogetComputedStyle(el).backgroundColor for every regiongetBoundingClientRect(), content via textContentIMPORTANT: Use el.getAttribute('class') || '' instead of el.className —
SVG elements return SVGAnimatedString for className, which breaks .substring().
Store all measurements as structured data before moving to Phase 2.
Switch to the Figma tab with chrome-devtools_select_page. Execute all Figma Plugin API code via chrome-devtools_evaluate_script, wrapped in (async () => { ... })() — top-level await is not supported in the Figma plugin eval context.
Build order:
figma.createFrame(), set exact page dimensions, layoutMode = 'NONE'Key Figma API rules:
await figma.loadFontAsync({family, style}) before creating/modifying texta (alpha) property — use opacity on the node insteadstrokesTop is not a valid property — use a 1px rectangle as a divider insteadlayoutMode = 'NONE' on parent frames for absolute positioning of childrenclipsContent = true on the table frameStart a CORS HTTP server to serve screenshots to Figma:
# /tmp/serve_screenshot.py
from http.server import HTTPServer, SimpleHTTPRequestHandler
import os
os.chdir('/tmp')
class CORSHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET')
self.send_header('Access-Control-Allow-Headers', '*')
super().end_headers()
def do_OPTIONS(self):
self.send_response(200)
self.end_headers()
HTTPServer(('', 9876), CORSHandler).serve_forever()
Switch to the live page tab and take a full-page screenshot via chrome-devtools_take_screenshot, save to /tmp/page-ref.png
Switch to the Figma tab and run chrome-devtools_evaluate_script to fetch the image and create an overlay rectangle:
(async () => {
const resp = await fetch('http://localhost:9876/page-ref.png');
const buf = await resp.arrayBuffer();
const img = figma.createImage(new Uint8Array(buf));
const overlay = figma.createRectangle();
overlay.name = 'OVERLAY_REF';
overlay.resize(frameWidth, frameHeight);
overlay.fills = [{ type: 'IMAGE', imageHash: img.hash, scaleMode: 'FILL' }];
overlay.blendMode = 'DIFFERENCE';
mainFrame.appendChild(overlay);
})()
Register window-level toggle utilities:
(async () => {
window.toggleOverlay = () => { overlay.visible = !overlay.visible; };
window.setOverlayOpacity = (v) => { overlay.opacity = v; };
window.overlayNormal = () => { overlay.blendMode = 'NORMAL'; };
window.overlayDiff = () => { overlay.blendMode = 'DIFFERENCE'; };
})()
Reading the overlay:
overlayNormal() at 50% opacity to see both layers simultaneouslyoverlayDiff() at 100% to spot even 1px offsetsUse chrome-devtools_take_screenshot on the Figma tab to see the overlay, then zoom into bright areas. For each:
chrome-devtools_evaluate_scriptchrome-devtools_evaluate_scriptCommon fixes needed:
After pixel-perfect alignment, refactor repeated patterns into Figma components:
Component creation pattern:
(async () => {
const comp = figma.createComponent();
comp.name = 'TableDataRow';
comp.resize(width, height);
comp.layoutMode = 'NONE';
comp.fills = [];
// Add children with positions relative to component...
// Then create instances:
const inst = comp.createInstance();
inst.name = 'Row_0';
inst.x = 0; inst.y = startY;
// Override text content:
inst.findOne(n => n.name === 'Label').characters = 'actual text';
parentFrame.appendChild(inst);
})()
CRITICAL component rules:
| Mistake | Fix |
|---|---|
await at top level | Wrap in (async () => { ... })() |
node.strokes = [{color: {r,g,b,a}}] | Remove a, use node.opacity instead |
node.strokesTop = ... | Not a real property — use a 1px rect divider |
el.className.substring(...) in CDP | Use el.getAttribute('class') || '' (SVG compat) |
Auto-layout + SPACE_BETWEEN for headers | Use layoutMode = 'NONE' for varied-width cells |
Setting child.x on instance children | Not allowed — bake positions into the component definition |
Calling figma.loadFontAsync once | Must call before EVERY text creation/modification |
window.toggleOverlay() stops working | Re-register window.* functions after page reload — node refs go stale |