Figma style binding
Skill senlindesign/claude2figma/.claude/skills/figma-style-binding
Triggers on any visual property change in Figma — creating text, setting colors, adjusting spacing/padding/gap/radius. Enforces that ALL values bind to Figma Styles or Variables, never hardcoded. Includes post-write QA verification.From its SKILL.md
npx -y skills add senlindesign/claude2figma --skill figma-style-bindingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
4.3 KB, as published. Nobody here has run it
Style Binding + QA
Every visual value must come from the design system. Supplements figma-generate-design. Prerequisite: figma-preflight must have run this session.
Binding Hierarchy
For any visual property, follow this order. Stop at the first match.
1. Connected Library → search_design_system → import → apply
2. Local Style → Style Registry → apply by ID
3. Local Variable → Variable Registry → apply by ID
4. Gap found → Report to user, wait for decision
Text
Every text node must use textStyleId. Individual font properties (fontSize, fontFamily, etc.) are forbidden.
const style = await figma.getStyleByIdAsync("<id>");
await figma.loadFontAsync(style.fontName);
node.textStyleId = "<id>";
If no local style matches, search libraries via search_design_system. If no match anywhere:
⚠️ Text style gap: no style for "[role]". Available: [top 5]. Use closest, or add missing style?
Color Fills
Every fill/stroke must bind to a COLOR Variable (preferred, supports theming) or Paint Style.
// Variable binding (preferred)
const variable = await figma.variables.getVariableByIdAsync("<id>");
const fill = { type: "SOLID", color: { r: 0, g: 0, b: 0 } };
node.fills = [figma.variables.setBoundVariableForPaint(fill, "color", variable)];
// Paint Style binding
node.fillStyleId = "<id>";
Never use raw { r, g, b } without a binding.
Spacing, Padding, Gap, Radius
Bind to FLOAT Variables. layoutMode must be set BEFORE setBoundVariable.
node.setBoundVariable("paddingTop", spacingVar);
node.setBoundVariable("paddingBottom", spacingVar);
node.setBoundVariable("paddingLeft", spacingVar);
node.setBoundVariable("paddingRight", spacingVar);
node.setBoundVariable("itemSpacing", spacingVar);
node.setBoundVariable("cornerRadius", radiusVar);
Spacing can fall back to raw values temporarily with user confirmation. Color and text cannot.
Forbidden / Required
| Forbidden | Required |
|---|---|
node.fontSize = 24 | node.textStyleId = id |
node.fills = [{ type: "SOLID", color: { r: .2, g: .4, b: 1 } }] | Variable or Style binding |
node.paddingLeft = 16 | node.setBoundVariable("paddingLeft", var) |
node.cornerRadius = 8 | node.setBoundVariable("cornerRadius", var) |
| Creating a Button from scratch | importComponentByKeyAsync from library |
QA Verification
After every use_figma call that creates or modifies nodes, run this verification on the returned node IDs:
const nodeIdsToAudit = [/* paste returned IDs */];
const results = [];
for (const id of nodeIdsToAudit) {
const node = await figma.getNodeByIdAsync(id);
if (!node) { results.push({ id, status: "NOT_FOUND" }); continue; }
const checks = [];
if (node.type === "TEXT") {
checks.push({ prop: "textStyleId", bound: !!node.textStyleId });
}
if ("fills" in node && Array.isArray(node.fills) && node.fills.length > 0) {
const bound = !!node.fillStyleId || (node.boundVariables?.fills?.length > 0);
checks.push({ prop: "fills", bound });
}
if ("layoutMode" in node && node.layoutMode !== "NONE") {
for (const p of ["paddingLeft","paddingRight","paddingTop","paddingBottom","itemSpacing"]) {
if (p in node) checks.push({ prop: p, bound: !!(node.boundVariables && p in node.boundVariables) });
}
}
if ("cornerRadius" in node && node.cornerRadius > 0) {
checks.push({ prop: "cornerRadius", bound: !!(node.boundVariables && "cornerRadius" in node.boundVariables) });
}
const failed = checks.filter(c => !c.bound);
results.push({ id, name: node.name, type: node.type, status: failed.length === 0 ? "PASS" : "FAIL", failed: failed.map(c => c.prop) });
}
return { auditResults: results };
If FAIL: Fix each unbound property using the binding rules above, then re-audit. Do not proceed to the next design step until all pass.
Report format:
✅ All [N] nodes passed.
// or
❌ FAIL "Card" (FRAME) — paddingTop, cornerRadius unbound. Fixing...