| name | figma-autolayout-system |
| description | **ALWAYS use this skill** whenever designing or editing frames in Figma. This skill enforces best practices for Auto Layout on every frame, binding variables and components from published libraries, and performing a visual composition review with library examples. Triggers: 'design a screen', 'create a layout', 'add a section', 'build a component', 'use auto layout', 'apply variables', 'use library components', 'design with tokens', 'maintain visual consistency', or any design task in Figma where frames, spacing, or components are involved. Never hardcode spacing, colors, or radii — always resolve and bind from the design system. Use alongside figma-use (mandatory) and figma-generate-design (for full screens).
|
Figma Auto Layout · Variables · Library Components · Visual Review
This skill defines the non-negotiable design standards for every frame created or edited in Figma:
- Auto Layout on every frame you create — no fixed-position layouts
- Variables from library — no hardcoded spacing, color, or radius values
- Components from library — no manually-drawn primitives when a component exists
- Visual composition review — screenshot + diff against library examples before finishing
MANDATORY: Always also load figma-use before any use_figma call. Its critical rules (color ranges 0–1, font loading, page context, etc.) apply to every script here.
Pass skillNames: "figma-autolayout-system" in every use_figma call (logging only, does not affect execution).
1. Auto Layout — Always On (for frames YOU create)
⚠️ FrameNode vs. InstanceNode — know the difference first
This is the most important distinction in this skill. Before touching any node's layoutMode, identify its type:
| Node type | How it exists | Set layoutMode? | Set padding/gap? |
|---|
FrameNode | figma.createFrame() — you built it | ✅ Always | ✅ Bind to variables |
ComponentNode | figma.createComponent() — you built it | ✅ Yes | ✅ Bind to variables |
InstanceNode | component.createInstance() — from the library | ❌ Never | ❌ Don't touch internals |
ComponentSetNode | The library's component set container | ❌ Never | ❌ Don't touch internals |
When you place a library component instance, its internal layout is owned by its master component. Never set layoutMode, paddingTop, paddingLeft, itemSpacing, or any other layout property directly on an InstanceNode. Doing so will visually corrupt or detach the component.
What you can do on an instance is control how it sits inside your auto-layout frame:
parent.appendChild(instance);
instance.layoutSizingHorizontal = "FILL";
instance.setProperties({ "Label#2:0": "Click" });
That's it. Never go further into the instance's internals.
You can guard against accidental layout changes with a type check:
if (node.type === "FRAME" || node.type === "COMPONENT") {
node.layoutMode = "VERTICAL";
}
Every FrameNode or ComponentNode you create must have Auto Layout enabled. No exceptions for frames you own.
Minimal auto-layout frame
const frame = figma.createFrame();
frame.name = "Section";
frame.layoutMode = "VERTICAL";
frame.primaryAxisSizingMode = "HUG";
frame.counterAxisSizingMode = "FIXED";
frame.resize(frame.width, frame.height);
frame.paddingTop = 24;
frame.paddingBottom = 24;
frame.paddingLeft = 24;
frame.paddingRight = 24;
frame.itemSpacing = 16;
frame.counterAxisAlignItems = "MIN";
frame.primaryAxisAlignItems = "MIN";
Sizing mode cheat-sheet
| Mode | primaryAxisSizingMode | counterAxisSizingMode | Notes |
|---|
| HUG content | HUG | HUG | Frame wraps around its children |
| Fixed width, hug height | HUG | FIXED | Common for cards |
| Full-width fill | HUG | FIXED (or set via parent) | Parent must be auto-layout |
| Explicit both | FIXED | FIXED | Use sparingly — prefer HUG |
FILL children
layoutSizingHorizontal/Vertical = 'FILL' must be set AFTER parent.appendChild(child).
This applies to both child frames AND child instances:
parent.appendChild(child);
child.layoutSizingHorizontal = "FILL";
Nesting rules
- Every child frame (not instance) of an auto-layout parent should itself be an auto-layout frame.
- Instances from the library slot into your auto-layout frames as children — their internal layout is not yours to change.
- Do not use absolute positioning (
isAbsolutePositionEnabled) except for overlays and decorative elements that intentionally float above the layout.
- Group nodes only for export — use nested auto-layout frames for structure.
2. Bind Variables — Never Hardcode
Discovery first
Before writing any value, resolve it from the library. See variable-resolution.md for a full resolution order and fallback chain.
Bind spacing / padding / gap
Only bind variables on frames/components you created — never on instances:
const spaceVar = await figma.variables.importVariableByKeyAsync("SPACE_VAR_KEY");
frame.setBoundVariable("paddingTop", spaceVar);
frame.setBoundVariable("paddingBottom", spaceVar);
frame.setBoundVariable("paddingLeft", spaceVar);
frame.setBoundVariable("paddingRight", spaceVar);
frame.setBoundVariable("itemSpacing", spaceVar);
Bind colors (fills / strokes)
setBoundVariableForPaint returns a new paint — always capture and reassign:
const colorVar = await figma.variables.importVariableByKeyAsync("BG_COLOR_VAR_KEY");
const currentFills = [...frame.fills];
const newFill = figma.variables.setBoundVariableForPaint(currentFills[0], "color", colorVar);
frame.fills = [newFill];
Bind border radius
const radiusVar = await figma.variables.importVariableByKeyAsync("RADIUS_VAR_KEY");
frame.setBoundVariable("topLeftRadius", radiusVar);
frame.setBoundVariable("topRightRadius", radiusVar);
frame.setBoundVariable("bottomLeftRadius", radiusVar);
frame.setBoundVariable("bottomRightRadius", radiusVar);
Variable scope reference
Use specific scopes — never default ALL_SCOPES:
| Variable type | Recommended scope(s) |
|---|
| Background fill | ["FRAME_FILL", "SHAPE_FILL"] |
| Text color | ["TEXT_FILL"] |
| Border/stroke | ["STROKE_COLOR"] |
| Gap / spacing | ["GAP"] |
| Padding | ["WIDTH_HEIGHT"] |
| Corner radius | ["CORNER_RADIUS"] |
3. Library Components — Always Import, Never Draw
Resolution order
- Inspect existing screens — walk instances in a reference frame to get authoritative keys.
- search_design_system — broad search with synonyms ("button", "btn", "cta", "action").
- Only build manually if nothing exists in the library for this element.
Import and instantiate
const btnSet = await figma.importComponentSetByKeyAsync("BUTTON_SET_KEY");
const primaryBtn = btnSet.children.find(c =>
c.name.toLowerCase().includes("variant=primary")
) || btnSet.defaultVariant;
const instance = primaryBtn.createInstance();
parent.appendChild(instance);
instance.layoutSizingHorizontal = "FILL";
What you can and cannot do with instances
instance.layoutSizingHorizontal = "FILL";
instance.layoutSizingVertical = "HUG";
instance.setProperties({ "Label#2:0": "Get Started" });
const nested = instance.findOne(n => n.type === "INSTANCE" && n.name === "Icon");
if (nested) nested.setProperties({ "icon#12:0": "arrow-right" });
instance.layoutMode = "HORIZONTAL";
instance.paddingTop = 16;
instance.itemSpacing = 8;
instance.primaryAxisSizingMode = "HUG";
The reason: instances are live links to their master component. Changing structural layout properties on an instance either silently corrupts the output or causes Figma to detach it, losing all the design system benefits.
Apply text + effect styles
const textStyle = await figma.importStyleByKeyAsync("TEXT_STYLE_KEY");
const shadowStyle = await figma.importStyleByKeyAsync("SHADOW_STYLE_KEY");
textNode.textStyleId = textStyle.id;
frame.effectStyleId = shadowStyle.id;
4. Visual Composition Review
After every section or significant change, take a screenshot and compare against the library. This is not optional.
Review checklist (run after each section)
[ ] All frames YOU CREATED have layoutMode !== "NONE" (instances are excluded from this check)
[ ] No hardcoded hex colors on frames you own — all fills bound to variables
[ ] No hardcoded spacing numbers on frames you own — all padding/gap bound to variables
[ ] No hardcoded radius on frames you own — all corners bound to variables
[ ] All UI elements are component instances (not raw shapes)
[ ] No layout properties (layoutMode, padding, itemSpacing) set on InstanceNodes
[ ] Text uses textStyleId (not manual font size / weight)
[ ] Effects (shadows, blurs) use effectStyleId
[ ] FILL children set after appendChild
[ ] No absolute-positioned nodes in structural frames
[ ] Screenshot passes visual diff (no clipping, no overlap, no placeholder text)
Screenshot per section
const sectionNode = await figma.getNodeByIdAsync("SECTION_NODE_ID");
Always screenshot at section level — full-page screenshots hide clipping and overflow.
Compare against library examples
Before finalising, visually compare the new section against an existing reference screen:
- Use
get_metadata on a reference frame to list its component instances and their keys.
- Confirm your new section uses the same component keys (not different variants).
- Use
get_screenshot on both the reference and the new section side-by-side.
- Fix any discrepancies: wrong variant, wrong spacing token, wrong text style.
For the full discovery and comparison workflow see visual-review.md.
5. Full Workflow Summary
1. Inspect the file
└─ List pages, existing screens, variable collections, component sets
2. Discover library assets
├─ search_design_system → spacing, color, radius variables
├─ search_design_system → components (button, card, input, nav…)
└─ Walk existing screen instances for authoritative keys
3. Build each section (one use_figma call per section)
├─ Create FrameNode with layoutMode = VERTICAL | HORIZONTAL
├─ Bind all padding / gap → spacing variables
├─ Bind all fills → color variables
├─ Bind all radii → radius variables
├─ Import library components → createInstance() → appendChild()
│ └─ Only set layoutSizingHorizontal/Vertical on instances, nothing else
├─ Apply text styles and effect styles
└─ Return { createdNodeIds, mutatedNodeIds }
4. After each section
└─ get_screenshot → check for clipping, overlap, placeholder text
5. After all sections
├─ get_screenshot on full page
├─ Compare against reference screen (component keys + visual diff)
└─ Fix any issues (targeted fix, not full rebuild)
6. Reference Docs
Load these as needed:
7. Quick Violation Reference
| ❌ Wrong | ✅ Correct |
|---|
frame.paddingTop = 24 (hardcoded on your frame) | frame.setBoundVariable("paddingTop", spaceVar) |
frame.fills = [{type:"SOLID", color:{r:0,g:0,b:0}}] | bind to color variable via setBoundVariableForPaint |
frame.cornerRadius = 8 (hardcoded) | frame.setBoundVariable("topLeftRadius", radiusVar) |
Creating a RectangleNode for a button background | Import button component set, create instance |
frame.layoutMode = "NONE" on a frame you created | Always set layoutMode = "VERTICAL" or "HORIZONTAL" |
instance.layoutMode = "HORIZONTAL" on a library instance | ❌ Never — instances own their layout internally |
instance.paddingTop = 16 on a library instance | ❌ Never — use setProperties() for overrides |
Setting FILL before appendChild | parent.appendChild(child) then child.layoutSizingHorizontal = "FILL" |
Skipping get_screenshot after a section | Always screenshot; check clipping, overlaps, placeholders |