スキル一覧に戻る
srstomp

figma-plugin

by srstomp

An orchestration plugin for Claude Code that enables reliable autonomous development sessions with configurable checkpoints and task management.

0🍴 0📅 2026年1月23日
GitHubで見るManusで実行

SKILL.md


name: figma-plugin description: Create Figma plugins with the Plugin API. Covers plugin architecture (sandbox + UI), node manipulation, styles, components, UI development with postMessage communication, and publishing. Use this skill when building Figma plugins, working with the Figma Plugin API, or creating design automation tools.

Figma Plugin Development

Build plugins that extend Figma's functionality.

Plugin Architecture

┌─────────────────────────────────────────────────────────────┐
│                      FIGMA APPLICATION                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────┐      ┌─────────────────────────┐  │
│  │    MAIN THREAD      │      │      UI THREAD          │  │
│  │    (Sandbox)        │      │      (iframe)           │  │
│  │                     │      │                         │  │
│  │  • Plugin API       │ ←──→ │  • HTML/CSS/JS          │  │
│  │  • Node access      │ post │  • User interface       │  │
│  │  • figma.*          │ Msg  │  • No Figma API access  │  │
│  │  • Read/write doc   │      │  • Can use npm packages │  │
│  │                     │      │                         │  │
│  └─────────────────────┘      └─────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Key Constraints:

  • Main thread (sandbox): Access to Figma API, but limited JS environment
  • UI thread (iframe): Full browser environment, but no Figma API access
  • Communication via postMessage / onmessage

Plugin Types

TypeUse CaseHas UI
Design PluginManipulate design filesOptional
FigJam PluginManipulate FigJam filesOptional
WidgetInteractive objects in canvasAlways (React-like)

Quick Start

Minimal Plugin (No UI)

// code.ts
// Runs in main thread with Figma API access

// Get current selection
const selection = figma.currentPage.selection;

if (selection.length === 0) {
  figma.notify('Please select something');
  figma.closePlugin();
}

// Do something with selection
for (const node of selection) {
  if ('fills' in node) {
    node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }];
  }
}

figma.notify(`Updated ${selection.length} items`);
figma.closePlugin();

Plugin with UI

// code.ts (main thread)
figma.showUI(__html__, { width: 300, height: 200 });

// Receive messages from UI
figma.ui.onmessage = (msg) => {
  if (msg.type === 'create-rectangle') {
    const rect = figma.createRectangle();
    rect.resize(msg.width, msg.height);
    rect.fills = [{ type: 'SOLID', color: { r: 0.5, g: 0.5, b: 1 } }];
    figma.currentPage.appendChild(rect);
    figma.viewport.scrollAndZoomIntoView([rect]);
  }

  if (msg.type === 'cancel') {
    figma.closePlugin();
  }
};
<!-- ui.html (UI thread) -->
<div id="app">
  <label>Width: <input id="width" type="number" value="100"></label>
  <label>Height: <input id="height" type="number" value="100"></label>
  <button id="create">Create Rectangle</button>
  <button id="cancel">Cancel</button>
</div>

<script>
  document.getElementById('create').onclick = () => {
    parent.postMessage({
      pluginMessage: {
        type: 'create-rectangle',
        width: Number(document.getElementById('width').value),
        height: Number(document.getElementById('height').value),
      }
    }, '*');
  };

  document.getElementById('cancel').onclick = () => {
    parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
  };
</script>

Core Concepts

Node Types

CategoryTypes
ContainersPageNode, FrameNode, GroupNode, SectionNode
ShapesRectangleNode, EllipseNode, PolygonNode, StarNode, LineNode, VectorNode
TextTextNode
ComponentsComponentNode, ComponentSetNode, InstanceNode
MediaImageNode (via fills)
SpecialBooleanOperationNode, SliceNode, ConnectorNode

Properties by Mixin

// Figma uses mixins for shared properties

// BlendMixin - opacity, blendMode, effects
node.opacity = 0.5;
node.effects = [{ type: 'DROP_SHADOW', ... }];

// GeometryMixin - fills, strokes
node.fills = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }];
node.strokes = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }];
node.strokeWeight = 2;

// LayoutMixin - position, size, constraints
node.x = 100;
node.y = 100;
node.resize(200, 100);
node.constraints = { horizontal: 'SCALE', vertical: 'CENTER' };

// ChildrenMixin - parent/child relationships
node.children; // readonly
node.appendChild(child);
node.insertChild(0, child);

Type Guards

// Check node type before accessing properties
function isTextNode(node: SceneNode): node is TextNode {
  return node.type === 'TEXT';
}

function hasChildren(node: SceneNode): node is FrameNode | GroupNode {
  return 'children' in node;
}

function hasFills(node: SceneNode): node is GeometryMixin & SceneNode {
  return 'fills' in node;
}

// Usage
for (const node of figma.currentPage.selection) {
  if (isTextNode(node)) {
    node.characters = 'Updated text';
  }
  if (hasFills(node)) {
    node.fills = [...];
  }
}

Communication Pattern

Main → UI

// code.ts
figma.ui.postMessage({ type: 'selection-changed', count: selection.length });
// ui.ts
window.onmessage = (event) => {
  const msg = event.data.pluginMessage;
  if (msg.type === 'selection-changed') {
    document.getElementById('count').textContent = msg.count;
  }
};

UI → Main

// ui.ts
parent.postMessage({ pluginMessage: { type: 'do-something', data: 'value' } }, '*');
// code.ts
figma.ui.onmessage = (msg) => {
  if (msg.type === 'do-something') {
    console.log(msg.data);
  }
};

Typed Messages

// shared-types.ts
type PluginMessage =
  | { type: 'create-shape'; shape: 'rectangle' | 'ellipse'; size: number }
  | { type: 'update-selection'; color: RGB }
  | { type: 'cancel' };

type UIMessage =
  | { type: 'selection-changed'; count: number; types: string[] }
  | { type: 'error'; message: string };

// code.ts
figma.ui.onmessage = (msg: PluginMessage) => {
  switch (msg.type) {
    case 'create-shape':
      // TypeScript knows msg has shape and size
      break;
    case 'update-selection':
      // TypeScript knows msg has color
      break;
  }
};

Common Operations

Create Nodes

// Shapes
const rect = figma.createRectangle();
const ellipse = figma.createEllipse();
const frame = figma.createFrame();

// Text (must load font first)
const text = figma.createText();
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
text.characters = 'Hello World';

// Component
const component = figma.createComponent();
component.name = 'Button';

// Instance
const instance = component.createInstance();

Modify Selection

// Get selection
const selection = figma.currentPage.selection;

// Set selection
figma.currentPage.selection = [node1, node2];

// Listen for changes
figma.on('selectionchange', () => {
  const newSelection = figma.currentPage.selection;
  figma.ui.postMessage({ type: 'selection', nodes: newSelection.map(n => n.name) });
});

Colors

// RGB (0-1 range, not 0-255)
const red: RGB = { r: 1, g: 0, b: 0 };
const blue: RGB = { r: 0, g: 0, b: 1 };

// With alpha
const semiTransparent: RGBA = { r: 1, g: 0, b: 0, a: 0.5 };

// Hex to RGB helper
function hexToRgb(hex: string): RGB {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
    r: parseInt(result[1], 16) / 255,
    g: parseInt(result[2], 16) / 255,
    b: parseInt(result[3], 16) / 255,
  } : { r: 0, g: 0, b: 0 };
}

// Apply solid fill
node.fills = [{ type: 'SOLID', color: hexToRgb('#FF5733') }];

Storage

// Per-document storage
await figma.clientStorage.setAsync('key', { data: 'value' });
const data = await figma.clientStorage.getAsync('key');

// Per-node storage (survives copy/paste)
node.setPluginData('key', JSON.stringify({ saved: true }));
const nodeData = JSON.parse(node.getPluginData('key') || '{}');

// Shared across plugins (use namespace)
node.setSharedPluginData('com.myplugin', 'key', 'value');

Anti-Patterns

❌ Forgetting Async Font Loading

// WRONG: Will fail
const text = figma.createText();
text.characters = 'Hello'; // Error: font not loaded

// RIGHT
const text = figma.createText();
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
text.characters = 'Hello';

❌ Modifying Readonly Arrays

// WRONG: fills is readonly
node.fills.push(newFill); // Error

// RIGHT: Create new array
node.fills = [...node.fills, newFill];

❌ Not Handling Selection Empty

// WRONG: Crashes if nothing selected
const node = figma.currentPage.selection[0];
node.name = 'Renamed'; // Error if selection empty

// RIGHT
const selection = figma.currentPage.selection;
if (selection.length === 0) {
  figma.notify('Please select something');
  return;
}

❌ Blocking Main Thread

// WRONG: Long loop blocks Figma
for (let i = 0; i < 10000; i++) {
  figma.createRectangle();
}

// RIGHT: Batch with setTimeout
async function createManyRectangles(count: number) {
  const batchSize = 100;
  for (let i = 0; i < count; i += batchSize) {
    for (let j = 0; j < Math.min(batchSize, count - i); j++) {
      figma.createRectangle();
    }
    // Yield to Figma
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

❌ Not Closing Plugin

// WRONG: Plugin hangs
figma.showUI(__html__);
// User can't close if no cancel button

// RIGHT: Always provide exit
figma.showUI(__html__);
figma.ui.onmessage = (msg) => {
  if (msg.type === 'close') {
    figma.closePlugin();
  }
};

Plugin Categories

CategoryExamplesKey APIs
GeneratorsLorem ipsum, icons, patternsfigma.create*, fills, text
UtilitiesRename layers, organize, cleanupSelection, traversal, properties
ImportersJSON to layers, spreadsheet datafigma.create*, positioning
ExportersDesign tokens, code generationTraversal, styles, properties
ConnectorsSync with external toolsfigma.clientStorage, fetch
AccessibilityContrast checker, a11y auditColors, text, traversal

References:

スコア

総合スコア

60/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です