AgentStack
SKILL verified MIT Self-run

Design Organize

skill-shub-rajput-design-superskills-design-organize · by shub-rajput

Use when organizing, restructuring, or cleaning up Figma design screens — labeled layouts, sub-sections, grouping. Triggers include requests to organize screens, arrange frames, clean up a section, add labels, group screens, re-format a layout, create subsections, move screens between sections, split a section into groups, restructure how screens are grouped, or any request involving a Figma sect…

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-shub-rajput-design-superskills-design-organize

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Design Organize? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Design Organize

Organize scattered design screens inside a Figma section or frame into a clean, labeled layout with optional sub-section grouping. Works on any node type — frames, groups, images, shapes, vectors. Handles both fresh organization and re-formatting existing layouts.

> Validation principle: Every use_figma write step must be followed by a get_screenshot check. Do not stack writes without visual verification. If a screenshot reveals a problem, fix it before proceeding.

Quick Reference

| Step | What | |------|------| | 0 | Prerequisites — Figma MCP required | | 1 | Parse user intent, ask only unresolved questions | | 2 | Read container, classify children (screens vs labels vs notes) | | 3 | Handle existing labels (ask: keep/update/remove) | | 4 | Arrange screens + optional sub-section grouping | | 5 | Validate arrangement (getscreenshot) | | 6 | Add/reposition labels | | 7 | Validate labels (getscreenshot) | | 8 | Resize section(s) to fit content | | 9 | Final validation (get_screenshot) | | 10 | Present result, offer adjustments |

Step 0: Prerequisites

  1. Figma MCP server — check if mcp__figma__use_figma tool is available. If NOT available, tell the user:

> This skill requires the Figma MCP server. Run: > `` > claude mcp add --scope user --transport http figma https://mcp.figma.com/mcp > `` > Then restart Claude Code.

Do NOT proceed without it.

  1. figma-use skill — load figma:figma-use via the Skill tool before any use_figma call.
  1. Figma Section gotchas — critical, cause silent failures:
  • Page discovery: getNodeById() works cross-page — it will find nodes on ANY loaded page, giving false positives. Use figma.currentPage.findOne(n => n.id === "") instead, which only returns nodes that actually live on the current page.
  • Page-level TEXT: Figma stores TEXT nodes placed inside SECTIONs as page-level children — they won't appear in section.children. Scan figma.currentPage.children for TEXT nodes whose absoluteBoundingBox falls within section bounds. These are existing labels (canvas-absolute coords — convert using section.absoluteBoundingBox).
  • Overlapping children: SECTION nodes may discard children that overlap existing frames between calls. Position new nodes to avoid overlaps.
  • Stop on repeated failure: If same operation fails twice, STOP and read back children before retrying.

Step 1: Parse User Intent

The user has already provided a Figma link to a section or frame. Extract fileKey and nodeId from the URL (convert - to : in nodeId).

Parse inline preferences first. Check if the user's message already answers any of these:

  • Labels preference (e.g., "no labels", "auto-label", "label these screens")
  • Grouping preference (e.g., "group by feature", "keep flat")

Only ask about unresolved choices. Combine remaining questions into one AskUserQuestion:

> Labels: > - A) Use element names (default, fast) > - B) Auto-detect from content (I'll screenshot each element and generate descriptive labels — great for screenshot dumps with meaningless filenames) > - C) No labels

Skip questions the user already answered inline.

Step 2: Read Container and Classify Children

Find the correct page and read the target node's children. Critical: the section/frame won't be found on the default page — you must iterate all pages.

Page discovery approach: Use a single use_figma call that iterates all pages, trying to find the section AND access its children. When the section is found on the correct page, its children will be populated.

// Use findOne to locate the page that OWNS this node.
// Do NOT use getNodeById — it returns nodes cross-page, giving false positives.
let targetPage = null;
for (const page of figma.root.children) {
  await figma.setCurrentPageAsync(page);
  if (figma.currentPage.findOne(n => n.id === "")) {
    targetPage = page;
    break;
  }
}

After finding the page, mention it so the user can catch wrong-page issues early (e.g., "Working on page [page name]").

Why findOne: getNodeById() works cross-page once pages load into memory — it returns nodes from ANY page, causing the loop to stop on page 1 regardless of where the section actually lives. findOne only searches descendants of the current page.

Avoid get_metadata for large sections — it can return 300K+ characters exceeding token limits. Use use_figma directly for child classification instead.

Once on the correct page, classify children:

const container = figma.getNodeById("");
const screens = [];
const existingLabels = [];
const existingNotes = [];

for (const child of container.children) {
  if (child.visible === false) continue;

  // Existing label: ANY text node that is a direct child of the container.
  // Do NOT rely on fontSize === 70 — it may return a Symbol for mixed styles
  // or the label may have been created at a different size.
  if (child.type === "TEXT") {
    existingLabels.push({
      id: child.id,
      name: child.name,
      characters: child.characters,
      x: child.x, y: child.y,
      width: child.width, height: child.height
    });
  }
  // Existing note: instance ≤450px wide, or frame named "Dev Note" ≤450px wide
  else if ((child.type === "INSTANCE" && child.width = sectionBounds.x && nb.x + nb.width = sectionBounds.y && nb.y + nb.height  a.x - b.x || a.y - b.y),
  existingLabels,
  existingNotes: existingNotes.length
};

Important: Store one screen ID (e.g., the first screen's id) as ` — you'll use it in the page-switch preamble for all subsequent use_figma` calls.

Classification rules:

| Type | Detection | |------|-----------| | Label | type === "TEXT" in section children OR page-level TEXT whose absoluteBoundingBox falls within section bounds (pageLevel: true) | | Note (instance) | type === "INSTANCE" AND `width Before I start making changes — please save a named version in Figma (File → Save to Version History) so you can revert if needed. Figma has no undo for MCP changes. > > Let me know when you're ready to proceed.

Wait for confirmation before continuing to Step 3.

Step 3: Handle Existing Labels and Notes

Do NOT delete existing labels. Always reuse them. This preserves node IDs, custom styling, and user edits.

If existing labels were found in Step 2, inform the user:

> Found X existing labels. I'll reposition them above their screens and update the text if needed.

Then in Step 6:

  1. Match each existing label to its nearest screen by x-proximity
  2. Reposition it above the matched screen
  3. Update .characters only if the label text doesn't match the screen name (and user chose Mode A) or with auto-detected text (Mode B)
  4. Create new labels only for screens that have no matching existing label
  5. If there are leftover labels with no matching screen, ask the user before removing them

For existing notes: same approach — reposition, don't delete.

If no existing labels/notes found: skip this step entirely.

Step 4: Arrange Screens

Layout defaults:

| Setting | Default | |---------|---------| | Section padding | 100px | | Label-to-screen gap | 70px | | Screen-to-screen gap | 200px | | Label font | Inter Medium, 70pt | | Label color | White {r:1, g:1, b:1} |

Flat layout (default)

Position all screens in a horizontal row:

// Switch page first
for (const page of figma.root.children) {
  await figma.setCurrentPageAsync(page);
  if (figma.currentPage.findOne(n => n.id === "")) break;
}

const container = figma.getNodeById("");

// Collect screens only (exclude text labels and notes)
const screens = container.children
  .filter(c => {
    if (c.visible === false) return false;
    if (c.type === "TEXT") return false;
    if (c.type === "INSTANCE" && c.width  a.x - b.x || a.y - b.y);

// Measure label height dynamically
await figma.loadFontAsync({ family: "Inter", style: "Medium" });
const tempLabel = figma.createText();
tempLabel.fontName = { family: "Inter", style: "Medium" };
tempLabel.fontSize = 70;
tempLabel.characters = "X";
const labelHeight = tempLabel.height;
tempLabel.remove();

const padding = 100;
const labelGap = 70;
// labelsEnabled = true if user chose A or B for labels in Step 1
const startY =  ? padding + labelHeight + labelGap : padding;

const screenToScreenGap = 200;

let currentX = padding;

for (const screen of screens) {
  screen.x = currentX;
  screen.y = startY;
  currentX += screen.width + screenToScreenGap;
}

return { arranged: screens.length, mutatedNodeIds: screens.map(s => s.id) };

Sub-section grouping (when user requests or 8+ screens)

For larger sets of screens, offer to group them into sub-sections. This can happen:

  • User asks explicitly (e.g., "group by feature area")
  • Auto-suggest for 8+ screens — analyze labels for common prefixes (text before >) and suggest groups

Grouping flow:

  1. Analyze screen names or auto-detected labels for common prefixes
  2. Present detected groups:

> Suggested groups (from label prefixes): > - Submissions — 4 screens > - Onboarding — 3 screens > - Settings — 3 screens > - Dashboard — 2 screens > > Layout: Vertical (sections stacked top to bottom) or Horizontal (side by side)?

  1. User confirms or adjusts

Creating sub-sections:

// Switch page
for (const page of figma.root.children) {
  await figma.setCurrentPageAsync(page);
  if (figma.currentPage.findOne(n => n.id === "")) break;
}

const parentSection = figma.getNodeById("");

// Create a child section for each group
const subSection = figma.createSection();
subSection.name = "";

// Move this group's screens into the sub-section
for (const screen of groupScreens) {
  const node = figma.getNodeById(screen.id);
  subSection.appendChild(node);
}

// Arrange screens horizontally within the sub-section (same logic as flat layout)
// ...

// Sub-section fill: darken parent fill ~30% for visual contrast
if (parentSection.fills && parentSection.fills.length > 0) {
  const parentFill = parentSection.fills[0];
  if (parentFill.type === "SOLID") {
    subSection.fills = [{ type: "SOLID", color: {
      r: parentFill.color.r * 0.7,
      g: parentFill.color.g * 0.7,
      b: parentFill.color.b * 0.7
    }}];
  } else {
    subSection.fills = [...parentSection.fills];
  }
}

parentSection.appendChild(subSection);

Positioning sub-sections (vertical stacking — default for 3+ groups):

const sectionGap = 100;
let currentY = padding;

for (const subSection of subSections) {
  subSection.x = padding;
  subSection.y = currentY;
  currentY += subSection.height + sectionGap;
}

Positioning sub-sections (horizontal — for 2 groups or when user prefers):

let currentX = padding;

for (const subSection of subSections) {
  subSection.x = currentX;
  subSection.y = padding;
  currentX += subSection.width + sectionGap;
}

Gotcha: sub-section children after creation. After creating sub-sections and moving children into them, reading subSection.children via the parent node may return empty arrays. To access sub-section children reliably, navigate via a known child ID: figma.getNodeById(childId).parent gives you the sub-section with populated children.

Step 5: Validate Arrangement

Call get_screenshot on the section/frame. Verify:

  • All screens horizontally aligned within each section/sub-section
  • Consistent spacing between screens
  • Sub-sections properly stacked (vertical or horizontal)
  • No overlapping elements

Fix any issues before proceeding.

Step 6: Add/Reposition Labels

First, determine the label text for each screen based on the user's label mode choice:

  • Mode A (element names): label text = screen.name
  • Mode B (auto-detect): call get_screenshot on each screen element (outside use_figma), analyze the content, generate a short descriptive label. Build a map: [{ screenId, labelText }, ...]

Auto-detect label guidelines:

  • Keep labels short (2-5 words)
  • Use > as a hierarchy separator for sub-views (e.g., "Share > Link")
  • Focus on the primary content, not UI chrome
  • For modals: name the modal (e.g., "Calendar Invitation Email")
  • For settings: name the category (e.g., "Limits & Buffers")

Then, apply labels non-destructively. If existing labels were found in Step 2, reuse them. Only create new text nodes for screens that don't have a matching label.

// Switch page first
for (const page of figma.root.children) {
  await figma.setCurrentPageAsync(page);
  const section = figma.getNodeById("");
  if (section && section.children && section.children.length > 0) break;
}

await figma.loadFontAsync({ family: "Inter", style: "Medium" });

const container = figma.getNodeById("");

// Collect screens and existing labels
const screens = [];
const existingLabels = [];
for (const child of container.children) {
  if (child.visible === false) continue;
  if (child.type === "TEXT") {
    existingLabels.push(child);
  } else if (!(child.type === "INSTANCE" && child.width = sectionBounds.x && nb.x + nb.width = sectionBounds.y && nb.y + nb.height  a.x - b.x || a.y - b.y);

// labelTexts is an array of strings, one per screen (from Mode A or B)
// Mode A: use screen names directly. Mode B: use auto-detected labels from Step 6.
const labelTexts = /* Mode A */ screens.map(s => s.name);
// OR for Mode B: labelMap is built earlier from get_screenshot analysis
// const labelTexts = screens.map(s => labelMap.get(s.id) || s.name);

// Get section's absolute position (needed for page-level labels)
const sectionAbs = container.absoluteBoundingBox;

// Match existing labels to screens by x-proximity, then reuse them
const usedLabels = new Set();
const mutatedIds = [];

for (let i = 0; i  !usedLabels.has(l.id));

return {
  reused: usedLabels.size,
  created: mutatedIds.length - usedLabels.size,
  orphaned: orphanedLabels.map(l => ({ id: l.id, text: l.characters })),
  mutatedNodeIds: mutatedIds
};

Font fallback: If Inter loading fails, try "Roboto" then "Arial". Inform the user which font was used.

Key rule: NEVER delete existing labels without explicit user consent. The code above reuses existing text nodes by updating .characters and repositioning. New text nodes are only created for screens that don't have a nearby existing label. Orphaned labels (no matching screen) are reported back — ask the user what to do with them.

Step 7: Validate Labels

Call get_screenshot on the section. Verify:

  • Labels appear above each screen, not overlapping
  • Label text is readable
  • Label spacing is consistent

Fix any issues before proceeding.

Step 8: Resize Section(s) to Fit Content

Calculate content bounds and resize. If sub-sections were created, resize each sub-section first, then the parent.

// Switch page first
for (const page of figma.root.children) {
  await figma.setCurrentPageAsync(page);
  if (figma.currentPage.findOne(n => n.id === "")) break;
}

// Resize a section to fit its content
function resizeToFit(section) {
  let maxX = 0, maxY = 0;
  for (const child of section.children) {
    const right = child.x + child.width;
    const bottom = child.y + child.height;
    if (right > maxX) maxX = right;
    if (bottom > maxY) maxY = bottom;
  }
  const padding = 100;
  section.resizeWithoutConstraints(maxX + padding, maxY + padding);
}

const parent = figma.getNodeById("");

// If sub-sections exist, resize each first
for (const child of parent.children) {
  if (child.type === "SECTION") resizeToFit(child);
}

// Then resize parent
resizeToFit(parent);

return { resized: true };

Step 9: Final Validation

Call get_screenshot on the full section/frame. Verify:

  • Screens aligned within each section/sub-section
  • Labels positioned above each screen with consis

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.