
Figma Design
- 403 installs
- 61 repo stars
- Updated June 13, 2026
- manutej/luxor-claude-marketplace
figma-design is a Claude agent skill that teaches the Figma Plugin API for building plugins, component systems, auto layout, variants, and design-token workflows for developers automating Figma design and handoff.
About
figma-design is a version 1.0.0 skill in manutej/luxor-claude-marketplace that gives coding agents deep Figma Plugin API knowledge sourced from official documentation via Context7 (/figma/plugin-typings). It covers plugin development, component architecture with variants and instances, auto layout constraints, variables and design tokens, prototyping reactions, batch node operations, and export to PNG, JPG, and SVG. Developers reach for figma-design when writing Figma plugin TypeScript, automating repetitive canvas tasks, or structuring design systems programmatically rather than pushing pixels manually. The luxor-design-toolkit skill documents node hierarchy patterns, findAllWithCriteria search, performance practices, and production plugin architecture including state management and command patterns. Install with npx skills add against the luxor-claude-marketplace repository when programmatic Figma work must pair with engineering handoff specs.
- Component sets and variants
- Auto-layout and responsive frames
- Design tokens and styles
- Prototype flows and interactions
- Developer handoff and specs
Figma Design by the numbers
- 403 all-time installs (skills.sh)
- +20 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #680 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill figma-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 403 |
|---|---|
| repo stars | ★ 61 |
| Last updated | June 13, 2026 |
| Repository | manutej/luxor-claude-marketplace ↗ |
How do you build Figma plugins with the Plugin API?
Translate product requirements into Figma frames, components, auto-layout, variants, and handoff specs before engineering commits to production UI.
Who is it for?
Frontend and design-system engineers writing Figma plugins or automating component libraries and auto layout with the Plugin API.
Skip if: Developers who only need manual UI mockups in Figma without plugin code, API automation, or design-system scripting.
When should I use this skill?
A developer asks to build a Figma plugin, automate auto layout, manage design tokens, or export assets via the Figma Plugin API.
What you get
Figma plugin TypeScript, component libraries, auto-layout frames, design-token variables, and exported PNG, JPG, or SVG assets
- figma plugin code
- component system spec
- exported design assets
By the numbers
- Published as version 1.0.0 in luxor-claude-marketplace skill frontmatter
- Documents export to 3 image formats: PNG, JPG, and SVG
Files
Figma Design Skill
Comprehensive guide for Figma design workflows, plugin development, component systems, auto layout, prototyping, and design system management based on official Figma Plugin API documentation from Context7.
When to Use This Skill
Use this skill when working with:
- Figma Plugin Development: Building custom plugins, UI extensions, automation tools
- Design Systems: Creating and managing variables, styles, components, and libraries
- Component Architecture: Building reusable components, variants, and instances
- Auto Layout: Implementing responsive frames with constraints and spacing
- Prototyping: Creating interactive prototypes with reactions and flows
- Batch Operations: Automating repetitive design tasks across multiple nodes
- Export & Integration: Exporting assets in various formats (PNG, JPG, SVG)
- Data Management: Storing and retrieving plugin/shared data on nodes
- Collaboration: Managing version history, comments, and team workflows
Core Concepts
1. Figma Node Hierarchy
The Figma document structure is a tree of nodes:
DocumentNode (root)
└── PageNode
├── FrameNode
│ ├── TextNode
│ ├── RectangleNode
│ └── ComponentNode
└── SectionNode
└── FrameNodeKey Node Types:
FRAME: Container with auto-layout capabilitiesCOMPONENT: Reusable design element (master)INSTANCE: Copy of a componentTEXT: Editable text layerRECTANGLE,ELLIPSE,POLYGON,STAR,LINE: Basic shapesSECTION: Organizational container for framesGROUP: Non-layout container
2. Components and Instances
Components are master elements that can be instantiated multiple times:
// Create a component
const button = figma.createComponent()
button.name = "Primary Button"
button.resize(120, 40)
// Add visual elements
const bg = figma.createRectangle()
bg.resize(120, 40)
bg.cornerRadius = 8
bg.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.5, b: 1 } }]
button.appendChild(bg)
// Create instance
const buttonInstance = button.createInstance()
buttonInstance.x = 200
buttonInstance.y = 100Component Properties:
BOOLEAN: Toggle visibility or statesTEXT: Customizable text contentINSTANCE_SWAP: Swap nested componentsVARIANT: Different component variations
3. Auto Layout
Auto Layout creates responsive frames that adapt to content changes:
Core Properties:
layoutMode: 'HORIZONTAL', 'VERTICAL', or 'NONE'primaryAxisSizingMode: 'FIXED' or 'AUTO'counterAxisSizingMode: 'FIXED' or 'AUTO'paddingLeft,paddingRight,paddingTop,paddingBottomitemSpacing: Gap between childrenprimaryAxisAlignItems: Alignment on main axiscounterAxisAlignItems: Alignment on cross axis
Constraints for Children:
minWidth,maxWidth: Width boundariesminHeight,maxHeight: Height boundarieslayoutAlign: 'MIN', 'CENTER', 'MAX', 'STRETCH'layoutGrow: 0 (fixed) or 1 (fill container)
// Create auto-layout frame
const frame = figma.createFrame()
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'FIXED'
frame.resize(300, 0) // Width fixed, height auto
frame.itemSpacing = 16
frame.paddingLeft = 24
frame.paddingRight = 24
frame.paddingTop = 24
frame.paddingBottom = 24
// Add children with constraints
const child = figma.createRectangle()
child.resize(252, 100)
child.layoutAlign = 'STRETCH' // Fill width
child.minHeight = 100
child.maxHeight = 200
frame.appendChild(child)4. Constraints
Constraints control how nodes resize when their parent changes:
interface Constraints {
horizontal: 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'SCALE'
vertical: 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'SCALE'
}
node.constraints = {
horizontal: 'MIN', // Pin to left
vertical: 'MAX' // Pin to bottom
}Constraint Types:
MIN: Pin to top/left edgeCENTER: Center in parentMAX: Pin to bottom/right edgeSTRETCH: Scale with parent (both edges)SCALE: Maintain proportional position and size
5. Variables and Design Tokens
Variables create dynamic design systems (Figma Design only):
// Create variable collection
const collection = figma.variables.createVariableCollection('Design Tokens')
// Create color variable
const primaryColor = figma.variables.createVariable(
'color/primary',
collection,
'COLOR'
)
// Set value for default mode
const defaultMode = collection.modes[0]
primaryColor.setValueForMode(defaultMode.modeId, {
r: 0.2, g: 0.5, b: 1, a: 1
})
// Add dark mode
const darkMode = collection.addMode('Dark')
primaryColor.setValueForMode(darkMode, {
r: 0.4, g: 0.7, b: 1, a: 1
})
// Create variable alias (reference)
const accentColor = figma.variables.createVariable(
'color/accent',
collection,
'COLOR'
)
const alias = figma.variables.createVariableAlias(primaryColor)
accentColor.setValueForMode(defaultMode.modeId, alias)
// Bind variable to node
const rect = figma.createRectangle()
const fill = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } } as SolidPaint
const boundFill = figma.variables.setBoundVariableForPaint(
fill,
'color',
primaryColor
)
rect.fills = [boundFill]Variable Types:
COLOR: RGB/RGBA valuesFLOAT: Numeric values (spacing, sizes)BOOLEAN: True/false flagsSTRING: Text values
6. Styles
Styles define reusable visual properties:
// Paint style (fills/strokes)
const paintStyle = figma.createPaintStyle()
paintStyle.name = 'Brand/Primary'
paintStyle.paints = [{
type: 'SOLID',
color: { r: 0.2, g: 0.5, b: 1 }
}]
// Text style
const textStyle = figma.createTextStyle()
textStyle.name = 'Heading/H1'
textStyle.fontSize = 32
textStyle.fontName = { family: 'Inter', style: 'Bold' }
textStyle.lineHeight = { value: 120, unit: 'PERCENT' }
textStyle.letterSpacing = { value: -0.5, unit: 'PIXELS' }
// Effect style (shadows, blurs)
const effectStyle = figma.createEffectStyle()
effectStyle.name = 'Shadow/Card'
effectStyle.effects = [{
type: 'DROP_SHADOW',
color: { r: 0, g: 0, b: 0, a: 0.15 },
offset: { x: 0, y: 4 },
radius: 8,
visible: true,
blendMode: 'NORMAL'
}]
// Apply styles
rect.fillStyleId = paintStyle.id
text.textStyleId = textStyle.id
frame.effectStyleId = effectStyle.idPrototyping
1. Reactions and Interactions
Reactions define interactive behavior in prototypes:
// Set reactions on a node
await node.setReactionsAsync([
{
action: {
type: 'NODE',
destinationId: 'nodeId', // Target frame
navigation: 'NAVIGATE',
transition: {
type: 'SMART_ANIMATE',
easing: { type: 'EASE_IN_AND_OUT' },
duration: 0.3
},
preserveScrollPosition: false
},
trigger: {
type: 'ON_CLICK'
}
}
])Trigger Types:
ON_CLICK: Click/tapON_HOVER: Mouse hoverON_PRESS: Touch pressON_DRAG: Drag interactionMOUSE_ENTER,MOUSE_LEAVE,MOUSE_UP,MOUSE_DOWNAFTER_TIMEOUT: Delayed trigger
Navigation Types:
NAVIGATE: Go to destinationSWAP: Swap overlayOVERLAY: Open as overlaySCROLL_TO: Scroll to positionCHANGE_TO: Change to state
2. Overlay Configuration
Control how frames appear as overlays:
frame.overlayPositionType // 'CENTER' | 'TOP_LEFT' | 'TOP_CENTER' | etc.
frame.overlayBackground // How overlay obscures background
frame.overlayBackgroundInteraction // Click-through behavior3. Scrolling Frames
Configure frame scrolling behavior:
frame.overflowDirection = 'VERTICAL_SCROLLING' // or 'HORIZONTAL_SCROLLING'
frame.numberOfFixedChildren = 1 // First N children stay fixed during scrollPlugin Development
1. Plugin Structure
Basic Plugin Files:
my-plugin/
├── manifest.json # Plugin configuration
├── code.ts # Main plugin logic
└── ui.html # Optional UImanifest.json:
{
"name": "My Plugin",
"id": "unique-plugin-id",
"api": "1.0.0",
"main": "code.js",
"ui": "ui.html",
"editorType": ["figma", "figjam"],
"documentAccess": "dynamic-page",
"networkAccess": {
"allowedDomains": ["api.example.com"]
}
}2. Plugin Lifecycle
// Initialize plugin
async function init() {
// Show UI (optional)
figma.showUI(__html__, {
width: 400,
height: 500,
title: "My Plugin",
themeColors: true
})
// Load saved preferences
const prefs = await figma.clientStorage.getAsync('preferences')
// Send initial data to UI
figma.ui.postMessage({
type: 'init',
data: prefs
})
}
// Handle UI messages
figma.ui.onmessage = async (msg) => {
if (msg.type === 'create-shapes') {
await createShapes(msg.count, msg.color)
}
if (msg.type === 'export') {
await exportSelection()
}
}
// Clean up on close
figma.on('close', () => {
console.log('Plugin closing...')
})
// Start plugin
init()3. UI Communication
Send message from plugin to UI:
figma.ui.postMessage({
type: 'selection-changed',
count: figma.currentPage.selection.length,
nodes: figma.currentPage.selection.map(n => ({
id: n.id,
name: n.name,
type: n.type
}))
})Receive messages in UI (ui.html):
<script>
window.onmessage = (event) => {
const msg = event.data.pluginMessage
if (msg.type === 'selection-changed') {
document.getElementById('count').textContent = msg.count
}
}
// Send message to plugin
function createShapes() {
parent.postMessage({
pluginMessage: {
type: 'create-shapes',
count: 5,
color: { r: 1, g: 0, b: 0 }
}
}, '*')
}
</script>4. Event Listeners
Monitor document and selection changes:
// Selection changes
figma.on('selectionchange', () => {
const selection = figma.currentPage.selection
console.log(`Selected ${selection.length} nodes`)
})
// Document changes
figma.on('documentchange', (event) => {
for (const change of event.documentChanges) {
if (change.type === 'CREATE') {
console.log(`Created: ${change.id}`)
}
if (change.type === 'DELETE') {
console.log(`Deleted: ${change.id}`)
}
if (change.type === 'PROPERTY_CHANGE') {
console.log(`Changed properties: ${change.properties.join(', ')}`)
}
}
})
// Page changes
figma.on('currentpagechange', () => {
console.log(`Now on page: ${figma.currentPage.name}`)
})5. Data Storage
Plugin Data (private to your plugin):
// Store data on node
node.setPluginData('status', 'approved')
node.setPluginData('metadata', JSON.stringify({
author: 'John',
tags: ['important']
}))
// Retrieve data
const status = node.getPluginData('status')
const metadata = JSON.parse(node.getPluginData('metadata') || '{}')
// List all keys
const keys = node.getPluginDataKeys()Shared Plugin Data (accessible by namespace):
// Store shared data
node.setSharedPluginData('com.example.plugin', 'version', '2.0')
// Retrieve shared data
const version = node.getSharedPluginData('com.example.plugin', 'version')
// List shared keys
const keys = node.getSharedPluginDataKeys('com.example.plugin')Client Storage (persistent settings):
// Save preferences
await figma.clientStorage.setAsync('preferences', {
theme: 'dark',
lastUsed: new Date().toISOString()
})
// Load preferences
const prefs = await figma.clientStorage.getAsync('preferences')
// Delete data
await figma.clientStorage.deleteAsync('preferences')
// List all keys
const keys = await figma.clientStorage.keysAsync()Finding Nodes
1. Optimized Search with findAllWithCriteria
FASTEST method for large documents (hundreds of times faster):
// Enable performance optimization
figma.skipInvisibleInstanceChildren = true
// Find by type
const textNodes = figma.currentPage.findAllWithCriteria({
types: ['TEXT']
})
// Find multiple types
const shapes = figma.currentPage.findAllWithCriteria({
types: ['RECTANGLE', 'ELLIPSE', 'POLYGON']
})
// Find nodes with plugin data
const nodesWithStatus = figma.currentPage.findAllWithCriteria({
pluginData: {
keys: ['status']
}
})
// Find nodes with shared plugin data
const nodesWithSharedData = figma.currentPage.findAllWithCriteria({
sharedPluginData: {
namespace: 'com.example.plugin',
keys: ['version']
}
})
// Combine criteria
const textWithData = figma.currentPage.findAllWithCriteria({
types: ['TEXT'],
pluginData: {} // Any plugin data
})2. Traditional Search Methods
// Find all matching nodes
const frames = figma.currentPage.findAll(node => node.type === 'FRAME')
// Find first match
const template = figma.currentPage.findOne(node =>
node.name.startsWith('Template')
)
// Find by ID
const node = await figma.getNodeByIdAsync('123:456')
// Recursive tree traversal
function walkTree(node: BaseNode) {
console.log(`${node.type}: ${node.name}`)
if ('children' in node) {
for (const child of node.children) {
walkTree(child)
}
}
}
walkTree(figma.currentPage)Export and Image Handling
1. Export Nodes
// Export as PNG
const pngBytes = await node.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 2 } // 2x resolution
})
// Export as JPG
const jpgBytes = await node.exportAsync({
format: 'JPG',
constraint: { type: 'SCALE', value: 1 },
contentsOnly: false // Include background
})
// Export as SVG
const svgBytes = await node.exportAsync({
format: 'SVG',
svgIdAttribute: true,
svgOutlineText: false,
svgSimplifyStroke: true
})
// Export with fixed dimensions
const thumbnail = await node.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 200 }
})
// Export with height constraint
const preview = await node.exportAsync({
format: 'PNG',
constraint: { type: 'HEIGHT', value: 400 }
})2. Load Images
// Load from URL
const image = await figma.createImageAsync('https://example.com/image.png')
const { width, height } = await image.getSizeAsync()
// Create rectangle with image
const rect = figma.createRectangle()
rect.resize(width, height)
rect.fills = [{
type: 'IMAGE',
imageHash: image.hash,
scaleMode: 'FILL' // or 'FIT', 'CROP', 'TILE'
}]
// Load from bytes
function loadFromBytes(bytes: Uint8Array) {
const image = figma.createImage(bytes)
return image
}
// Create from SVG string
const svgString = `
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" fill="#3366ff"/>
</svg>
`
const node = figma.createNodeFromSvg(svgString)Text Handling
1. Text Basics
// Create text (MUST load font first)
const text = figma.createText()
await figma.loadFontAsync(text.fontName) // Load default font
// Set content
text.characters = 'Hello World'
// Change font
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
text.fontName = { family: 'Inter', style: 'Bold' }
// Styling
text.fontSize = 24
text.lineHeight = { value: 150, unit: 'PERCENT' }
text.letterSpacing = { value: 0, unit: 'PIXELS' }
text.textAlignHorizontal = 'CENTER'
text.textAlignVertical = 'CENTER'
text.textCase = 'UPPER' // or 'LOWER', 'TITLE', 'ORIGINAL'
text.textDecoration = 'UNDERLINE' // or 'STRIKETHROUGH', 'NONE'2. Rich Text Formatting
// Apply formatting to range
text.setRangeFontSize(0, 5, 32) // First 5 chars = 32px
text.setRangeFills(0, 5, [{
type: 'SOLID',
color: { r: 1, g: 0, b: 0 }
}])
// Get font at position
const fontName = text.getRangeFontName(0, 1)
const fontSize = text.getRangeFontSize(0, 1)Resizing and Transforms
1. Resizing Methods
// Resize with constraints applied
node.resize(300, 200)
// Resize without constraints
node.resizeWithoutConstraints(300, 200)
// Scale proportionally
node.rescale(1.5) // 150% scale2. Transforms
// Position
node.x = 100
node.y = 200
// Relative to parent
console.log(node.relativeTransform) // [[1,0,x], [0,1,y]]
// Absolute position on page
console.log(node.absoluteTransform)
// Bounding box
const bounds = node.absoluteBoundingBox
// { x: number, y: number, width: number, height: number }Collaboration Features
1. Comments
While plugins can't create comments via API, they can:
- Navigate to frames with comments
- Read comment metadata via REST API
- Trigger notifications
2. Version History
// Plugin data can track custom versioning
node.setPluginData('version', '2.1.0')
node.setPluginData('lastModified', new Date().toISOString())
node.setPluginData('modifiedBy', 'user@example.com')3. Team Libraries
// Check if node is from external library
if (node.type === 'INSTANCE') {
const component = node.mainComponent
if (component && component.parent?.type === 'PAGE') {
// Component is from library
}
}Best Practices from Context7
1. Performance Optimization
// ✅ Use findAllWithCriteria for large documents
const nodes = figma.currentPage.findAllWithCriteria({
types: ['TEXT']
})
// ❌ Avoid findAll for simple type searches
const nodes = figma.currentPage.findAll(n => n.type === 'TEXT')
// ✅ Enable invisible instance optimization
figma.skipInvisibleInstanceChildren = true
// ✅ Batch operations
const nodes = []
for (let i = 0; i < 100; i++) {
const rect = figma.createRectangle()
rect.x = i * 120
nodes.push(rect)
}
figma.currentPage.selection = nodes
figma.viewport.scrollAndZoomIntoView(nodes)
// ❌ Avoid individual viewport updates
for (let i = 0; i < 100; i++) {
const rect = figma.createRectangle()
figma.viewport.scrollAndZoomIntoView([rect]) // Slow!
}2. Font Loading
// ✅ Always load fonts before modifying text
const text = figma.createText()
await figma.loadFontAsync(text.fontName)
text.characters = 'Hello'
// ✅ Load font before changing fontName
await figma.loadFontAsync({ family: 'Roboto', style: 'Bold' })
text.fontName = { family: 'Roboto', style: 'Bold' }
// ❌ This will error
text.characters = 'Hello' // Error: font not loaded3. Error Handling
try {
const node = await figma.getNodeByIdAsync(id)
if (!node) {
figma.notify('Node not found', { error: true })
return
}
// Process node
} catch (error) {
figma.notify(`Error: ${error.message}`, { error: true })
console.error(error)
}4. Memory Management
// ✅ Clean up large data
figma.on('close', () => {
// Clear large caches
cache.clear()
})
// ✅ Use async iteration for large datasets
async function processManyNodes() {
const nodes = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] })
for (let i = 0; i < nodes.length; i++) {
await processNode(nodes[i])
// Yield to UI every 100 items
if (i % 100 === 0) {
await new Promise(resolve => setTimeout(resolve, 0))
}
}
}5. Type Safety
// ✅ Type guards
if (node.type === 'TEXT') {
// TypeScript knows node is TextNode
console.log(node.characters)
}
if ('children' in node) {
// Node has children
node.children.forEach(child => console.log(child.name))
}
// ✅ Use specific types
function processText(node: TextNode) {
console.log(node.characters)
}
// ❌ Avoid any
function processNode(node: any) { // Bad!
console.log(node.characters) // Might error
}Common Workflows
1. Design System Setup
async function setupDesignSystem() {
// Create variable collection
const tokens = figma.variables.createVariableCollection('Design Tokens')
// Colors
const colors = {
'color/primary': { r: 0.2, g: 0.5, b: 1, a: 1 },
'color/secondary': { r: 0.5, g: 0.2, b: 0.8, a: 1 },
'color/success': { r: 0.2, g: 0.8, b: 0.3, a: 1 },
'color/error': { r: 0.9, g: 0.2, b: 0.2, a: 1 }
}
for (const [name, value] of Object.entries(colors)) {
const variable = figma.variables.createVariable(name, tokens, 'COLOR')
variable.setValueForMode(tokens.modes[0].modeId, value)
}
// Spacing
const spacing = [4, 8, 12, 16, 24, 32, 48, 64]
spacing.forEach((value, i) => {
const variable = figma.variables.createVariable(
`spacing/${i}`,
tokens,
'FLOAT'
)
variable.setValueForMode(tokens.modes[0].modeId, value)
})
// Create styles
const primaryStyle = figma.createPaintStyle()
primaryStyle.name = 'Color/Primary'
primaryStyle.paints = [{
type: 'SOLID',
color: colors['color/primary']
}]
figma.notify('Design system created!')
}2. Component Library
async function createButtonLibrary() {
// Create component set for variants
const buttons = figma.createComponentSet()
buttons.name = "Button"
// Primary variant
const primary = figma.createComponent()
primary.name = "Type=Primary, Size=Medium"
primary.resize(120, 40)
buttons.appendChild(primary)
// Secondary variant
const secondary = figma.createComponent()
secondary.name = "Type=Secondary, Size=Medium"
secondary.resize(120, 40)
buttons.appendChild(secondary)
// Large size
const large = figma.createComponent()
large.name = "Type=Primary, Size=Large"
large.resize(160, 48)
buttons.appendChild(large)
return buttons
}3. Batch Export
async function exportAllFrames() {
const frames = figma.currentPage.findAllWithCriteria({
types: ['FRAME']
})
const exports = []
for (const frame of frames) {
// Export 1x and 2x
const bytes1x = await frame.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 1 }
})
const bytes2x = await frame.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 2 }
})
exports.push({
name: frame.name,
'1x': bytes1x,
'2x': bytes2x
})
}
// Send to UI for download
figma.ui.postMessage({
type: 'exports-ready',
exports
})
}4. Style Sync
async function syncStyles() {
const paintStyles = await figma.getLocalPaintStylesAsync()
const textStyles = await figma.getLocalTextStylesAsync()
// Update all instances
const nodes = figma.currentPage.findAllWithCriteria({
types: ['RECTANGLE', 'TEXT']
})
for (const node of nodes) {
if (node.type === 'RECTANGLE') {
// Apply paint style based on name
const style = paintStyles.find(s => s.name === 'Brand/Primary')
if (style) {
node.fillStyleId = style.id
}
}
if (node.type === 'TEXT') {
// Apply text style
const style = textStyles.find(s => s.name === 'Body/Regular')
if (style) {
node.textStyleId = style.id
}
}
}
figma.notify('Styles synced!')
}Plugin UI Patterns
1. Theme-Aware UI
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 16px;
font-family: 'Inter', sans-serif;
background: var(--figma-color-bg);
color: var(--figma-color-text);
}
button {
background: var(--figma-color-bg-brand);
color: var(--figma-color-text-onbrand);
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background: var(--figma-color-bg-brand-hover);
}
input {
background: var(--figma-color-bg);
color: var(--figma-color-text);
border: 1px solid var(--figma-color-border);
padding: 8px;
border-radius: 4px;
}
</style>
</head>
<body>
<h2>My Plugin</h2>
<input type="text" id="input" placeholder="Enter text...">
<button onclick="handleClick()">Create</button>
</body>
</html>2. Loading States
<div id="status">
<div class="spinner"></div>
<p>Processing...</p>
</div>
<script>
function showLoading() {
document.getElementById('status').style.display = 'block'
}
function hideLoading() {
document.getElementById('status').style.display = 'none'
}
async function process() {
showLoading()
parent.postMessage({ pluginMessage: { type: 'process' } }, '*')
}
window.onmessage = (event) => {
if (event.data.pluginMessage.type === 'complete') {
hideLoading()
}
}
</script>FigJam-Specific Features
if (figma.editorType === 'figjam') {
// Create sticky note
const sticky = figma.createSticky()
sticky.x = 100
sticky.y = 100
await figma.loadFontAsync({ family: "Roboto", style: "Regular" })
sticky.text.characters = "TODO: Review designs"
// Create connector
const connector = figma.createConnector()
connector.connectorStart = {
endpointNodeId: sticky.id,
position: { x: 0, y: 0.5 }
}
// Create shape
const shape = figma.createShape()
shape.resize(100, 100)
shape.fills = [{ type: 'SOLID', color: { r: 1, g: 0.8, b: 0 } }]
}Advanced Techniques
1. Smart Component Swapping
async function swapComponents(oldComponent: ComponentNode, newComponent: ComponentNode) {
const instances = figma.currentPage.findAllWithCriteria({
types: ['INSTANCE']
})
let swapCount = 0
for (const instance of instances) {
if (instance.mainComponent?.id === oldComponent.id) {
await instance.swapAsync(newComponent)
swapCount++
}
}
figma.notify(`Swapped ${swapCount} instances`)
}2. Auto Layout Migration
function convertToAutoLayout(frame: FrameNode) {
// Store original positions
const childData = frame.children.map(child => ({
node: child,
x: child.x,
y: child.y
}))
// Enable auto layout
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'FIXED'
frame.itemSpacing = 16
frame.paddingLeft = 24
frame.paddingRight = 24
frame.paddingTop = 24
frame.paddingBottom = 24
// Configure children
frame.children.forEach(child => {
if ('layoutAlign' in child) {
child.layoutAlign = 'STRETCH'
}
})
}3. Responsive Resize
function makeResponsive(frame: FrameNode) {
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'FIXED'
// Make children responsive
frame.children.forEach(child => {
if ('resize' in child) {
child.layoutAlign = 'STRETCH'
child.layoutGrow = 0
child.minWidth = 200
child.maxWidth = 800
}
})
}Testing and Debugging
// Console logging
console.log('Selection:', figma.currentPage.selection)
console.error('Error occurred:', error)
// Notifications
figma.notify('Success!', { timeout: 2000 })
figma.notify('Error occurred', { error: true })
// Debugging helpers
function debugNode(node: SceneNode) {
console.log('Node Debug Info:')
console.log(' Type:', node.type)
console.log(' Name:', node.name)
console.log(' ID:', node.id)
console.log(' Position:', { x: node.x, y: node.y })
if ('width' in node) {
console.log(' Size:', { width: node.width, height: node.height })
}
if ('children' in node) {
console.log(' Children:', node.children.length)
}
}Resources
- Official Figma Plugin API: https://www.figma.com/plugin-docs/
- Context7 Library: /figma/plugin-typings (Trust Score: 9.8)
- Plugin Samples: https://github.com/figma/plugin-samples
- Community Forum: https://forum.figma.com/
- Widget API: https://www.figma.com/widget-docs/
- REST API: https://www.figma.com/developers/api
Summary
This skill covers:
- Component and instance management
- Auto layout and constraints
- Variables and design tokens
- Styles (paint, text, effect)
- Prototyping and interactions
- Plugin development patterns
- Node search and manipulation
- Export and image handling
- Performance optimization
- Best practices from Context7 research
Use this skill for building production-ready Figma plugins, automating design workflows, managing design systems, and creating scalable component libraries.
Figma Design Examples
Comprehensive collection of practical Figma plugin examples, design system patterns, and automation workflows based on official Figma Plugin API documentation from Context7.
Table of Contents
1. Component Creation Examples 2. Auto Layout Examples 3. Design System Examples 4. Plugin Development Examples 5. Data Management Examples 6. Export Examples 7. Prototyping Examples 8. Batch Operations Examples 9. Advanced Patterns
---
Component Creation Examples
Example 1: Create Button Component with Variants
async function createButtonSystem() {
// Create component set for variants
const buttonSet = figma.createComponentSet()
buttonSet.name = "Button"
buttonSet.x = 100
buttonSet.y = 100
// Variant configurations
const variants = [
{ type: 'Primary', size: 'Small', width: 100, height: 32, fontSize: 12 },
{ type: 'Primary', size: 'Medium', width: 120, height: 40, fontSize: 14 },
{ type: 'Primary', size: 'Large', width: 140, height: 48, fontSize: 16 },
{ type: 'Secondary', size: 'Small', width: 100, height: 32, fontSize: 12 },
{ type: 'Secondary', size: 'Medium', width: 120, height: 40, fontSize: 14 },
{ type: 'Secondary', size: 'Large', width: 140, height: 48, fontSize: 16 }
]
const colorMap = {
Primary: { r: 0.2, g: 0.5, b: 1 },
Secondary: { r: 0.5, g: 0.5, b: 0.5 }
}
for (const variant of variants) {
// Create component
const component = figma.createComponent()
component.name = `Type=${variant.type}, Size=${variant.size}`
component.resize(variant.width, variant.height)
// Create background
const bg = figma.createRectangle()
bg.name = "Background"
bg.resize(variant.width, variant.height)
bg.cornerRadius = 8
bg.fills = [{
type: 'SOLID',
color: colorMap[variant.type]
}]
component.appendChild(bg)
// Create text
const text = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' })
text.name = "Label"
text.characters = "Button"
text.fontSize = variant.fontSize
text.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
// Center text
text.x = (variant.width - text.width) / 2
text.y = (variant.height - text.height) / 2
component.appendChild(text)
// Add to component set
buttonSet.appendChild(component)
}
figma.currentPage.selection = [buttonSet]
figma.viewport.scrollAndZoomIntoView([buttonSet])
figma.notify('Button system created with 6 variants!')
return buttonSet
}Example 2: Icon Component Library
async function createIconLibrary() {
const icons = {
'home': 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
'user': 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2',
'settings': 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z'
}
const iconComponents = []
let xOffset = 0
for (const [name, path] of Object.entries(icons)) {
// Create SVG wrapper
const svgString = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="${path}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`
const iconNode = figma.createNodeFromSvg(svgString)
// Convert to component
const component = figma.createComponentFromNode(iconNode)
component.name = `Icon/${name}`
component.x = xOffset
component.y = 100
iconComponents.push(component)
xOffset += 50
}
figma.currentPage.selection = iconComponents
figma.viewport.scrollAndZoomIntoView(iconComponents)
figma.notify(`Created ${iconComponents.length} icon components!`)
return iconComponents
}Example 3: Card Component with Instance Swap
async function createCardComponent() {
// Create base card component
const card = figma.createComponent()
card.name = "Card"
card.resize(300, 400)
card.layoutMode = 'VERTICAL'
card.primaryAxisSizingMode = 'AUTO'
card.itemSpacing = 16
card.paddingLeft = 24
card.paddingRight = 24
card.paddingTop = 24
card.paddingBottom = 24
card.cornerRadius = 12
card.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
card.effects = [{
type: 'DROP_SHADOW',
color: { r: 0, g: 0, b: 0, a: 0.1 },
offset: { x: 0, y: 4 },
radius: 12,
visible: true,
blendMode: 'NORMAL'
}]
// Add image placeholder
const imagePlaceholder = figma.createRectangle()
imagePlaceholder.name = "Image"
imagePlaceholder.resize(252, 200)
imagePlaceholder.cornerRadius = 8
imagePlaceholder.fills = [{ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.9 } }]
if ('layoutAlign' in imagePlaceholder) {
imagePlaceholder.layoutAlign = 'STRETCH'
}
card.appendChild(imagePlaceholder)
// Add title
const title = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
title.name = "Title"
title.characters = "Card Title"
title.fontSize = 20
if ('layoutAlign' in title) {
title.layoutAlign = 'STRETCH'
}
card.appendChild(title)
// Add description
const description = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })
description.name = "Description"
description.characters = "Card description goes here with more details about the content."
description.fontSize = 14
description.textAlignVertical = 'TOP'
if ('layoutAlign' in description) {
description.layoutAlign = 'STRETCH'
}
card.appendChild(description)
// Add component properties
card.addComponentProperty('title', 'TEXT', 'Card Title')
card.addComponentProperty('description', 'TEXT', 'Card description')
card.addComponentProperty('showImage', 'BOOLEAN', true)
figma.notify('Card component created!')
return card
}---
Auto Layout Examples
Example 4: Responsive Navigation Bar
async function createNavBar() {
// Create nav container
const nav = figma.createFrame()
nav.name = "Navigation Bar"
nav.layoutMode = 'HORIZONTAL'
nav.primaryAxisSizingMode = 'FIXED'
nav.counterAxisSizingMode = 'FIXED'
nav.resize(1200, 64)
nav.itemSpacing = 24
nav.paddingLeft = 32
nav.paddingRight = 32
nav.primaryAxisAlignItems = 'SPACE_BETWEEN'
nav.counterAxisAlignItems = 'CENTER'
nav.fills = [{ type: 'SOLID', color: { r: 0.1, g: 0.1, b: 0.1 } }]
// Logo section
const logo = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
logo.name = "Logo"
logo.characters = "LOGO"
logo.fontSize = 24
logo.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
nav.appendChild(logo)
// Menu items container
const menu = figma.createFrame()
menu.name = "Menu"
menu.layoutMode = 'HORIZONTAL'
menu.primaryAxisSizingMode = 'AUTO'
menu.counterAxisSizingMode = 'FIXED'
menu.resize(0, 40)
menu.itemSpacing = 32
menu.fills = []
menu.layoutGrow = 1
const menuItems = ['Home', 'About', 'Services', 'Contact']
for (const item of menuItems) {
const menuItem = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' })
menuItem.name = item
menuItem.characters = item
menuItem.fontSize = 16
menuItem.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
menu.appendChild(menuItem)
}
nav.appendChild(menu)
// CTA Button
const cta = figma.createFrame()
cta.name = "CTA"
cta.layoutMode = 'HORIZONTAL'
cta.primaryAxisSizingMode = 'AUTO'
cta.counterAxisSizingMode = 'FIXED'
cta.resize(0, 40)
cta.paddingLeft = 24
cta.paddingRight = 24
cta.cornerRadius = 8
cta.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.5, b: 1 } }]
cta.primaryAxisAlignItems = 'CENTER'
cta.counterAxisAlignItems = 'CENTER'
const ctaText = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' })
ctaText.characters = "Get Started"
ctaText.fontSize = 14
ctaText.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
cta.appendChild(ctaText)
nav.appendChild(cta)
figma.currentPage.selection = [nav]
figma.viewport.scrollAndZoomIntoView([nav])
figma.notify('Navigation bar created!')
return nav
}Example 5: Grid Layout System
async function createGridLayout(rows: number, cols: number) {
const container = figma.createFrame()
container.name = `Grid ${rows}x${cols}`
container.layoutMode = 'VERTICAL'
container.primaryAxisSizingMode = 'AUTO'
container.counterAxisSizingMode = 'FIXED'
container.resize(800, 0)
container.itemSpacing = 16
container.paddingLeft = 24
container.paddingRight = 24
container.paddingTop = 24
container.paddingBottom = 24
container.fills = [{ type: 'SOLID', color: { r: 0.95, g: 0.95, b: 0.95 } }]
for (let row = 0; row < rows; row++) {
const rowFrame = figma.createFrame()
rowFrame.name = `Row ${row + 1}`
rowFrame.layoutMode = 'HORIZONTAL'
rowFrame.primaryAxisSizingMode = 'FIXED'
rowFrame.counterAxisSizingMode = 'AUTO'
rowFrame.resize(752, 0)
rowFrame.itemSpacing = 16
rowFrame.fills = []
if ('layoutAlign' in rowFrame) {
rowFrame.layoutAlign = 'STRETCH'
}
for (let col = 0; col < cols; col++) {
const cell = figma.createFrame()
cell.name = `Cell ${row + 1}-${col + 1}`
cell.layoutMode = 'VERTICAL'
cell.primaryAxisSizingMode = 'AUTO'
cell.counterAxisSizingMode = 'FIXED'
cell.resize((752 - (cols - 1) * 16) / cols, 0)
cell.paddingLeft = 16
cell.paddingRight = 16
cell.paddingTop = 16
cell.paddingBottom = 16
cell.cornerRadius = 8
cell.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
cell.layoutGrow = 1
// Add content
const text = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })
text.characters = `Cell ${row + 1}-${col + 1}`
text.fontSize = 14
if ('layoutAlign' in text) {
text.layoutAlign = 'STRETCH'
}
cell.appendChild(text)
rowFrame.appendChild(cell)
}
container.appendChild(rowFrame)
}
figma.currentPage.selection = [container]
figma.viewport.scrollAndZoomIntoView([container])
figma.notify(`${rows}x${cols} grid created!`)
return container
}
// Usage
createGridLayout(3, 4) // 3 rows, 4 columnsExample 6: Flexible Dashboard Layout
async function createDashboard() {
const dashboard = figma.createFrame()
dashboard.name = "Dashboard"
dashboard.layoutMode = 'VERTICAL'
dashboard.primaryAxisSizingMode = 'FIXED'
dashboard.counterAxisSizingMode = 'FIXED'
dashboard.resize(1200, 800)
dashboard.itemSpacing = 24
dashboard.paddingLeft = 24
dashboard.paddingRight = 24
dashboard.paddingTop = 24
dashboard.paddingBottom = 24
dashboard.fills = [{ type: 'SOLID', color: { r: 0.98, g: 0.98, b: 0.98 } }]
// Header
const header = figma.createFrame()
header.name = "Header"
header.layoutMode = 'HORIZONTAL'
header.primaryAxisSizingMode = 'FIXED'
header.counterAxisSizingMode = 'FIXED'
header.resize(1152, 80)
header.paddingLeft = 32
header.paddingRight = 32
header.primaryAxisAlignItems = 'SPACE_BETWEEN'
header.counterAxisAlignItems = 'CENTER'
header.cornerRadius = 12
header.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
const title = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
title.characters = "Dashboard Overview"
title.fontSize = 28
header.appendChild(title)
dashboard.appendChild(header)
// Stats row
const statsRow = figma.createFrame()
statsRow.name = "Stats"
statsRow.layoutMode = 'HORIZONTAL'
statsRow.primaryAxisSizingMode = 'FIXED'
statsRow.counterAxisSizingMode = 'AUTO'
statsRow.resize(1152, 0)
statsRow.itemSpacing = 24
statsRow.fills = []
if ('layoutAlign' in statsRow) {
statsRow.layoutAlign = 'STRETCH'
}
const stats = [
{ label: 'Total Users', value: '12,456' },
{ label: 'Revenue', value: '$45,678' },
{ label: 'Growth', value: '+23.5%' },
{ label: 'Active', value: '8,234' }
]
for (const stat of stats) {
const statCard = figma.createFrame()
statCard.name = stat.label
statCard.layoutMode = 'VERTICAL'
statCard.primaryAxisSizingMode = 'AUTO'
statCard.counterAxisSizingMode = 'FIXED'
statCard.resize(270, 0)
statCard.paddingLeft = 24
statCard.paddingRight = 24
statCard.paddingTop = 20
statCard.paddingBottom = 20
statCard.itemSpacing = 8
statCard.cornerRadius = 12
statCard.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
statCard.layoutGrow = 1
const label = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })
label.characters = stat.label
label.fontSize = 14
label.fills = [{ type: 'SOLID', color: { r: 0.5, g: 0.5, b: 0.5 } }]
statCard.appendChild(label)
const value = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
value.characters = stat.value
value.fontSize = 32
statCard.appendChild(value)
statsRow.appendChild(statCard)
}
dashboard.appendChild(statsRow)
// Main content area
const content = figma.createFrame()
content.name = "Content"
content.layoutMode = 'HORIZONTAL'
content.primaryAxisSizingMode = 'FIXED'
content.counterAxisSizingMode = 'FIXED'
content.resize(1152, 580)
content.itemSpacing = 24
content.fills = []
if ('layoutAlign' in content) {
content.layoutAlign = 'STRETCH'
}
// Chart area (2/3 width)
const chartArea = figma.createFrame()
chartArea.name = "Chart"
chartArea.resize(752, 580)
chartArea.cornerRadius = 12
chartArea.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
chartArea.layoutGrow = 2
content.appendChild(chartArea)
// Sidebar (1/3 width)
const sidebar = figma.createFrame()
sidebar.name = "Sidebar"
sidebar.resize(376, 580)
sidebar.cornerRadius = 12
sidebar.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
sidebar.layoutGrow = 1
content.appendChild(sidebar)
dashboard.appendChild(content)
figma.currentPage.selection = [dashboard]
figma.viewport.scrollAndZoomIntoView([dashboard])
figma.notify('Dashboard created!')
return dashboard
}---
Design System Examples
Example 7: Complete Design Token System
async function createDesignTokens() {
const collection = figma.variables.createVariableCollection('Design System')
const defaultMode = collection.modes[0]
defaultMode.name = 'Light'
const darkMode = collection.addMode('Dark')
// Color tokens
const colorTokens = {
'color/primary': {
light: { r: 0.2, g: 0.5, b: 1, a: 1 },
dark: { r: 0.4, g: 0.7, b: 1, a: 1 }
},
'color/secondary': {
light: { r: 0.5, g: 0.2, b: 0.8, a: 1 },
dark: { r: 0.7, g: 0.4, b: 0.9, a: 1 }
},
'color/background': {
light: { r: 1, g: 1, b: 1, a: 1 },
dark: { r: 0.1, g: 0.1, b: 0.1, a: 1 }
},
'color/surface': {
light: { r: 0.98, g: 0.98, b: 0.98, a: 1 },
dark: { r: 0.15, g: 0.15, b: 0.15, a: 1 }
},
'color/text/primary': {
light: { r: 0, g: 0, b: 0, a: 1 },
dark: { r: 1, g: 1, b: 1, a: 1 }
},
'color/text/secondary': {
light: { r: 0.5, g: 0.5, b: 0.5, a: 1 },
dark: { r: 0.7, g: 0.7, b: 0.7, a: 1 }
},
'color/success': {
light: { r: 0.2, g: 0.8, b: 0.3, a: 1 },
dark: { r: 0.3, g: 0.9, b: 0.4, a: 1 }
},
'color/warning': {
light: { r: 1, g: 0.7, b: 0, a: 1 },
dark: { r: 1, g: 0.8, b: 0.2, a: 1 }
},
'color/error': {
light: { r: 0.9, g: 0.2, b: 0.2, a: 1 },
dark: { r: 1, g: 0.3, b: 0.3, a: 1 }
}
}
const colorVars = {}
for (const [name, values] of Object.entries(colorTokens)) {
const variable = figma.variables.createVariable(name, collection, 'COLOR')
variable.setValueForMode(defaultMode.modeId, values.light)
variable.setValueForMode(darkMode, values.dark)
colorVars[name] = variable
}
// Spacing tokens
const spacingValues = [0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128]
const spacingVars = []
for (let i = 0; i < spacingValues.length; i++) {
const variable = figma.variables.createVariable(
`spacing/${i}`,
collection,
'FLOAT'
)
variable.setValueForMode(defaultMode.modeId, spacingValues[i])
variable.setValueForMode(darkMode, spacingValues[i])
spacingVars.push(variable)
}
// Typography tokens
const fontSizes = [12, 14, 16, 18, 20, 24, 28, 32, 40, 48, 64]
const typographyVars = []
for (let i = 0; i < fontSizes.length; i++) {
const variable = figma.variables.createVariable(
`typography/size/${i}`,
collection,
'FLOAT'
)
variable.setValueForMode(defaultMode.modeId, fontSizes[i])
variable.setValueForMode(darkMode, fontSizes[i])
typographyVars.push(variable)
}
// Border radius tokens
const borderRadii = [0, 4, 8, 12, 16, 24, 999]
const radiusVars = []
for (let i = 0; i < borderRadii.length; i++) {
const variable = figma.variables.createVariable(
`radius/${i}`,
collection,
'FLOAT'
)
variable.setValueForMode(defaultMode.modeId, borderRadii[i])
variable.setValueForMode(darkMode, borderRadii[i])
radiusVars.push(variable)
}
// Create semantic color aliases
const bgVariable = figma.variables.createVariable(
'semantic/background',
collection,
'COLOR'
)
const bgAlias = figma.variables.createVariableAlias(colorVars['color/background'])
bgVariable.setValueForMode(defaultMode.modeId, bgAlias)
bgVariable.setValueForMode(darkMode, bgAlias)
figma.notify(`Design tokens created! ${Object.keys(colorTokens).length} colors, ${spacingValues.length} spacing values, ${fontSizes.length} font sizes`)
return {
collection,
colorVars,
spacingVars,
typographyVars,
radiusVars
}
}Example 8: Typography Style System
async function createTypographySystem() {
const styles = [
{ name: 'Heading/H1', family: 'Inter', style: 'Bold', size: 48, lineHeight: 120 },
{ name: 'Heading/H2', family: 'Inter', style: 'Bold', size: 40, lineHeight: 120 },
{ name: 'Heading/H3', family: 'Inter', style: 'Bold', size: 32, lineHeight: 125 },
{ name: 'Heading/H4', family: 'Inter', style: 'Bold', size: 24, lineHeight: 130 },
{ name: 'Heading/H5', family: 'Inter', style: 'Bold', size: 20, lineHeight: 135 },
{ name: 'Heading/H6', family: 'Inter', style: 'Bold', size: 18, lineHeight: 140 },
{ name: 'Body/Large', family: 'Inter', style: 'Regular', size: 18, lineHeight: 150 },
{ name: 'Body/Medium', family: 'Inter', style: 'Regular', size: 16, lineHeight: 150 },
{ name: 'Body/Small', family: 'Inter', style: 'Regular', size: 14, lineHeight: 150 },
{ name: 'Caption/Regular', family: 'Inter', style: 'Regular', size: 12, lineHeight: 140 },
{ name: 'Caption/Bold', family: 'Inter', style: 'Bold', size: 12, lineHeight: 140 },
{ name: 'Overline', family: 'Inter', style: 'Medium', size: 10, lineHeight: 140 }
]
const textStyles = []
for (const styleConfig of styles) {
const textStyle = figma.createTextStyle()
textStyle.name = styleConfig.name
textStyle.fontName = {
family: styleConfig.family,
style: styleConfig.style
}
textStyle.fontSize = styleConfig.size
textStyle.lineHeight = {
value: styleConfig.lineHeight,
unit: 'PERCENT'
}
textStyle.letterSpacing = {
value: 0,
unit: 'PIXELS'
}
textStyles.push(textStyle)
}
// Create sample text nodes for preview
const preview = figma.createFrame()
preview.name = "Typography Preview"
preview.layoutMode = 'VERTICAL'
preview.primaryAxisSizingMode = 'AUTO'
preview.counterAxisSizingMode = 'FIXED'
preview.resize(600, 0)
preview.itemSpacing = 24
preview.paddingLeft = 32
preview.paddingRight = 32
preview.paddingTop = 32
preview.paddingBottom = 32
preview.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
for (const style of textStyles) {
const text = figma.createText()
await figma.loadFontAsync(style.fontName)
text.textStyleId = style.id
text.characters = style.name
if ('layoutAlign' in text) {
text.layoutAlign = 'STRETCH'
}
preview.appendChild(text)
}
figma.currentPage.selection = [preview]
figma.viewport.scrollAndZoomIntoView([preview])
figma.notify(`Created ${textStyles.length} text styles!`)
return textStyles
}Example 9: Color Palette with Paint Styles
async function createColorPalette() {
const palette = {
'Primary': {
'50': { r: 0.93, g: 0.95, b: 1 },
'100': { r: 0.86, g: 0.91, b: 1 },
'200': { r: 0.73, g: 0.82, b: 1 },
'300': { r: 0.60, g: 0.73, b: 1 },
'400': { r: 0.47, g: 0.64, b: 1 },
'500': { r: 0.2, g: 0.5, b: 1 },
'600': { r: 0.16, g: 0.4, b: 0.8 },
'700': { r: 0.12, g: 0.3, b: 0.6 },
'800': { r: 0.08, g: 0.2, b: 0.4 },
'900': { r: 0.04, g: 0.1, b: 0.2 }
},
'Gray': {
'50': { r: 0.98, g: 0.98, b: 0.98 },
'100': { r: 0.96, g: 0.96, b: 0.96 },
'200': { r: 0.93, g: 0.93, b: 0.93 },
'300': { r: 0.87, g: 0.87, b: 0.87 },
'400': { r: 0.74, g: 0.74, b: 0.74 },
'500': { r: 0.62, g: 0.62, b: 0.62 },
'600': { r: 0.46, g: 0.46, b: 0.46 },
'700': { r: 0.38, g: 0.38, b: 0.38 },
'800': { r: 0.26, g: 0.26, b: 0.26 },
'900': { r: 0.13, g: 0.13, b: 0.13 }
}
}
const paintStyles = []
let yOffset = 0
for (const [colorName, shades] of Object.entries(palette)) {
let xOffset = 0
for (const [shade, color] of Object.entries(shades)) {
// Create paint style
const paintStyle = figma.createPaintStyle()
paintStyle.name = `${colorName}/${shade}`
paintStyle.paints = [{
type: 'SOLID',
color: color
}]
paintStyles.push(paintStyle)
// Create preview swatch
const swatch = figma.createRectangle()
swatch.name = `${colorName}-${shade}`
swatch.resize(80, 80)
swatch.x = xOffset
swatch.y = yOffset
swatch.fillStyleId = paintStyle.id
swatch.cornerRadius = 8
xOffset += 90
}
yOffset += 90
}
figma.notify(`Created ${paintStyles.length} paint styles!`)
return paintStyles
}---
Plugin Development Examples
Example 10: Selection Inspector Plugin
// code.ts
figma.showUI(__html__, { width: 350, height: 500, themeColors: true })
function inspectSelection() {
const selection = figma.currentPage.selection
if (selection.length === 0) {
figma.ui.postMessage({
type: 'inspection',
data: { message: 'No selection' }
})
return
}
const inspectionData = selection.map(node => {
const data: any = {
id: node.id,
name: node.name,
type: node.type,
visible: node.visible,
locked: node.locked
}
if ('x' in node) {
data.position = { x: node.x, y: node.y }
}
if ('width' in node) {
data.size = { width: node.width, height: node.height }
}
if ('fills' in node) {
data.fills = node.fills
}
if ('strokes' in node) {
data.strokes = node.strokes
data.strokeWeight = node.strokeWeight
}
if ('cornerRadius' in node) {
data.cornerRadius = node.cornerRadius
}
if ('opacity' in node) {
data.opacity = node.opacity
}
if ('layoutMode' in node) {
data.autoLayout = {
mode: node.layoutMode,
spacing: node.itemSpacing,
padding: {
left: node.paddingLeft,
right: node.paddingRight,
top: node.paddingTop,
bottom: node.paddingBottom
}
}
}
if (node.type === 'TEXT') {
data.text = {
characters: node.characters,
fontSize: node.fontSize,
fontName: node.fontName
}
}
return data
})
figma.ui.postMessage({
type: 'inspection',
data: inspectionData
})
}
figma.on('selectionchange', inspectSelection)
inspectSelection()
figma.ui.onmessage = async (msg) => {
if (msg.type === 'select-node') {
const node = await figma.getNodeByIdAsync(msg.id)
if (node && 'x' in node) {
figma.currentPage.selection = [node]
figma.viewport.scrollAndZoomIntoView([node])
}
}
}<!-- ui.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
padding: 16px;
font-family: 'Inter', sans-serif;
background: var(--figma-color-bg);
color: var(--figma-color-text);
font-size: 12px;
}
.node-card {
background: var(--figma-color-bg-secondary);
padding: 12px;
border-radius: 6px;
margin-bottom: 12px;
cursor: pointer;
}
.node-card:hover {
background: var(--figma-color-bg-hover);
}
.node-type {
font-size: 10px;
color: var(--figma-color-text-secondary);
text-transform: uppercase;
}
.node-name {
font-weight: 600;
margin: 4px 0;
}
.property {
display: flex;
justify-content: space-between;
margin: 4px 0;
font-size: 11px;
}
.property-label {
color: var(--figma-color-text-secondary);
}
</style>
</head>
<body>
<div id="content"></div>
<script>
window.onmessage = (event) => {
const msg = event.data.pluginMessage
if (msg.type === 'inspection') {
renderInspection(msg.data)
}
}
function renderInspection(data) {
const content = document.getElementById('content')
if (data.message) {
content.innerHTML = `<p>${data.message}</p>`
return
}
content.innerHTML = data.map(node => `
<div class="node-card" onclick="selectNode('${node.id}')">
<div class="node-type">${node.type}</div>
<div class="node-name">${node.name}</div>
${node.position ? `
<div class="property">
<span class="property-label">Position</span>
<span>x: ${node.position.x.toFixed(0)}, y: ${node.position.y.toFixed(0)}</span>
</div>
` : ''}
${node.size ? `
<div class="property">
<span class="property-label">Size</span>
<span>${node.size.width.toFixed(0)} × ${node.size.height.toFixed(0)}</span>
</div>
` : ''}
${node.opacity !== undefined ? `
<div class="property">
<span class="property-label">Opacity</span>
<span>${(node.opacity * 100).toFixed(0)}%</span>
</div>
` : ''}
${node.cornerRadius !== undefined ? `
<div class="property">
<span class="property-label">Corner Radius</span>
<span>${node.cornerRadius}</span>
</div>
` : ''}
</div>
`).join('')
}
function selectNode(id) {
parent.postMessage({
pluginMessage: { type: 'select-node', id }
}, '*')
}
</script>
</body>
</html>Example 11: Batch Rename Plugin
// code.ts
figma.showUI(__html__, { width: 400, height: 300 })
figma.ui.onmessage = async (msg) => {
if (msg.type === 'rename') {
const { find, replace, useRegex, caseSensitive } = msg
const selection = figma.currentPage.selection
if (selection.length === 0) {
figma.notify('Please select nodes to rename', { error: true })
return
}
let renamed = 0
const flags = caseSensitive ? 'g' : 'gi'
for (const node of selection) {
const oldName = node.name
if (useRegex) {
try {
const regex = new RegExp(find, flags)
node.name = node.name.replace(regex, replace)
} catch (error) {
figma.notify('Invalid regex pattern', { error: true })
return
}
} else {
if (caseSensitive) {
node.name = node.name.split(find).join(replace)
} else {
const regex = new RegExp(find, 'gi')
node.name = node.name.replace(regex, replace)
}
}
if (node.name !== oldName) {
renamed++
}
}
figma.notify(`Renamed ${renamed} of ${selection.length} nodes`)
}
if (msg.type === 'close') {
figma.closePlugin()
}
}Example 12: Style Applier Plugin
// code.ts
figma.showUI(__html__, { width: 400, height: 500 })
async function loadStyles() {
const paintStyles = await figma.getLocalPaintStylesAsync()
const textStyles = await figma.getLocalTextStylesAsync()
const effectStyles = await figma.getLocalEffectStylesAsync()
figma.ui.postMessage({
type: 'styles-loaded',
data: {
paint: paintStyles.map(s => ({ id: s.id, name: s.name })),
text: textStyles.map(s => ({ id: s.id, name: s.name })),
effect: effectStyles.map(s => ({ id: s.id, name: s.name }))
}
})
}
figma.ui.onmessage = async (msg) => {
if (msg.type === 'apply-style') {
const { styleId, styleType } = msg
const selection = figma.currentPage.selection
if (selection.length === 0) {
figma.notify('Please select nodes', { error: true })
return
}
let applied = 0
for (const node of selection) {
try {
if (styleType === 'paint' && 'fillStyleId' in node) {
node.fillStyleId = styleId
applied++
} else if (styleType === 'text' && node.type === 'TEXT') {
node.textStyleId = styleId
applied++
} else if (styleType === 'effect' && 'effectStyleId' in node) {
node.effectStyleId = styleId
applied++
}
} catch (error) {
console.error('Error applying style:', error)
}
}
figma.notify(`Applied style to ${applied} nodes`)
}
}
loadStyles()---
Data Management Examples
Example 13: Version Control System
interface VersionData {
version: string
timestamp: string
author: string
changes: string
snapshot: string
}
async function saveVersion(node: SceneNode, message: string) {
// Get current versions
const versionsJson = node.getPluginData('versions') || '[]'
const versions: VersionData[] = JSON.parse(versionsJson)
// Create new version
const newVersion: VersionData = {
version: `v${versions.length + 1}`,
timestamp: new Date().toISOString(),
author: 'current-user',
changes: message,
snapshot: JSON.stringify({
name: node.name,
type: node.type,
properties: extractProperties(node)
})
}
versions.push(newVersion)
// Save back to node
node.setPluginData('versions', JSON.stringify(versions))
node.setPluginData('currentVersion', newVersion.version)
figma.notify(`Version ${newVersion.version} saved!`)
return newVersion
}
function extractProperties(node: SceneNode): Record<string, any> {
const props: any = {}
if ('x' in node) {
props.position = { x: node.x, y: node.y }
}
if ('width' in node) {
props.size = { width: node.width, height: node.height }
}
if ('fills' in node) {
props.fills = node.fills
}
if ('opacity' in node) {
props.opacity = node.opacity
}
return props
}
async function listVersions(node: SceneNode): Promise<VersionData[]> {
const versionsJson = node.getPluginData('versions') || '[]'
return JSON.parse(versionsJson)
}
// Usage
const selectedNode = figma.currentPage.selection[0]
await saveVersion(selectedNode, 'Updated button styling')
const versions = await listVersions(selectedNode)
console.log('Versions:', versions)Example 14: Design Status Tracker
type DesignStatus = 'draft' | 'in-review' | 'approved' | 'published'
interface StatusData {
status: DesignStatus
updatedAt: string
updatedBy: string
comments: string
}
async function setDesignStatus(
node: SceneNode,
status: DesignStatus,
comments: string = ''
) {
const statusData: StatusData = {
status,
updatedAt: new Date().toISOString(),
updatedBy: 'current-user',
comments
}
node.setPluginData('design-status', JSON.stringify(statusData))
// Set visual indicator
if ('strokes' in node) {
const statusColors: Record<DesignStatus, RGB> = {
'draft': { r: 0.5, g: 0.5, b: 0.5 },
'in-review': { r: 1, g: 0.7, b: 0 },
'approved': { r: 0.2, g: 0.8, b: 0.3 },
'published': { r: 0.2, g: 0.5, b: 1 }
}
node.strokes = [{
type: 'SOLID',
color: statusColors[status]
}]
node.strokeWeight = 3
}
figma.notify(`Status set to: ${status}`)
}
async function getDesignStatus(node: SceneNode): Promise<StatusData | null> {
const data = node.getPluginData('design-status')
return data ? JSON.parse(data) : null
}
async function findNodesByStatus(status: DesignStatus): Promise<SceneNode[]> {
const allNodes = figma.currentPage.findAllWithCriteria({
pluginData: {
keys: ['design-status']
}
})
const filtered: SceneNode[] = []
for (const node of allNodes) {
const statusData = await getDesignStatus(node)
if (statusData && statusData.status === status) {
filtered.push(node)
}
}
return filtered
}
// Usage
const frame = figma.currentPage.selection[0]
await setDesignStatus(frame, 'in-review', 'Ready for review')
const reviewNodes = await findNodesByStatus('in-review')
console.log(`Found ${reviewNodes.length} nodes in review`)Example 15: Component Usage Analytics
interface UsageMetrics {
instanceCount: number
lastUsed: string
usedInPages: string[]
variantUsage: Record<string, number>
}
async function trackComponentUsage(component: ComponentNode): Promise<UsageMetrics> {
const instances = figma.currentPage.findAllWithCriteria({
types: ['INSTANCE']
}).filter(instance => instance.mainComponent?.id === component.id)
const usedInPages = new Set<string>()
const variantUsage: Record<string, number> = {}
for (const instance of instances) {
// Track page usage
if (instance.parent?.type === 'PAGE') {
usedInPages.add(instance.parent.name)
}
// Track variant usage
if (instance.variantProperties) {
const variantKey = JSON.stringify(instance.variantProperties)
variantUsage[variantKey] = (variantUsage[variantKey] || 0) + 1
}
}
const metrics: UsageMetrics = {
instanceCount: instances.length,
lastUsed: new Date().toISOString(),
usedInPages: Array.from(usedInPages),
variantUsage
}
// Save metrics to component
component.setPluginData('usage-metrics', JSON.stringify(metrics))
return metrics
}
async function generateUsageReport(): Promise<string> {
const components = figma.currentPage.findAllWithCriteria({
types: ['COMPONENT']
})
let report = 'Component Usage Report\n'
report += '======================\n\n'
for (const component of components) {
const metrics = await trackComponentUsage(component)
report += `${component.name}\n`
report += ` Instances: ${metrics.instanceCount}\n`
report += ` Pages: ${metrics.usedInPages.join(', ')}\n`
report += ` Last Used: ${metrics.lastUsed}\n\n`
}
return report
}
// Usage
const report = await generateUsageReport()
console.log(report)---
Export Examples
Example 16: Multi-Format Asset Exporter
async function exportAssets() {
const selection = figma.currentPage.selection
if (selection.length === 0) {
figma.notify('Please select nodes to export', { error: true })
return
}
const exports: Array<{
name: string
formats: Record<string, Uint8Array>
}> = []
for (const node of selection) {
const nodeExports: Record<string, Uint8Array> = {}
// PNG exports at different scales
nodeExports['png-1x'] = await node.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 1 }
})
nodeExports['png-2x'] = await node.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 2 }
})
nodeExports['png-3x'] = await node.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 3 }
})
// JPG export
nodeExports['jpg'] = await node.exportAsync({
format: 'JPG',
constraint: { type: 'SCALE', value: 2 }
})
// SVG export
nodeExports['svg'] = await node.exportAsync({
format: 'SVG',
svgIdAttribute: true,
svgOutlineText: false,
svgSimplifyStroke: true
})
// PDF export
nodeExports['pdf'] = await node.exportAsync({
format: 'PDF'
})
exports.push({
name: node.name,
formats: nodeExports
})
}
// Send to UI for download
figma.ui.postMessage({
type: 'exports-ready',
exports: exports.map(exp => ({
name: exp.name,
formats: Object.fromEntries(
Object.entries(exp.formats).map(([format, bytes]) => [
format,
Array.from(bytes)
])
)
}))
})
figma.notify(`Exported ${exports.length} assets in multiple formats`)
}Example 17: Icon Set Exporter
async function exportIconSet() {
// Find all components with "Icon/" prefix
const iconComponents = figma.currentPage.findAllWithCriteria({
types: ['COMPONENT']
}).filter(comp => comp.name.startsWith('Icon/'))
if (iconComponents.length === 0) {
figma.notify('No icon components found', { error: true })
return
}
const iconExports = []
for (const icon of iconComponents) {
const iconName = icon.name.replace('Icon/', '')
// Export as SVG
const svg = await icon.exportAsync({
format: 'SVG',
svgIdAttribute: true,
svgOutlineText: false,
svgSimplifyStroke: true
})
// Export as PNG at multiple sizes
const png24 = await icon.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 24 }
})
const png48 = await icon.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 48 }
})
const png96 = await icon.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 96 }
})
iconExports.push({
name: iconName,
svg: Array.from(svg),
png24: Array.from(png24),
png48: Array.from(png48),
png96: Array.from(png96)
})
}
// Send to UI
figma.ui.postMessage({
type: 'icon-exports-ready',
icons: iconExports
})
figma.notify(`Exported ${iconExports.length} icons`)
}Example 18: Screenshot Generator
async function generateScreenshots() {
const frames = figma.currentPage.findAllWithCriteria({
types: ['FRAME']
}).filter(frame => frame.name.startsWith('Screen/'))
const screenshots = []
for (const frame of frames) {
const screenName = frame.name.replace('Screen/', '')
// Desktop screenshot (1920x1080)
const desktop = await frame.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 1920 }
})
// Tablet screenshot (1024x768)
const tablet = await frame.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 1024 }
})
// Mobile screenshot (375x667)
const mobile = await frame.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 375 }
})
// Thumbnail (400px wide)
const thumbnail = await frame.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 400 }
})
screenshots.push({
name: screenName,
desktop: Array.from(desktop),
tablet: Array.from(tablet),
mobile: Array.from(mobile),
thumbnail: Array.from(thumbnail)
})
}
figma.ui.postMessage({
type: 'screenshots-ready',
screenshots
})
figma.notify(`Generated ${screenshots.length} screenshot sets`)
}---
Prototyping Examples
Example 19: Interaction Builder
async function createInteractionFlow() {
const frames = figma.currentPage.selection.filter(
node => node.type === 'FRAME'
) as FrameNode[]
if (frames.length < 2) {
figma.notify('Please select at least 2 frames', { error: true })
return
}
// Create navigation flow
for (let i = 0; i < frames.length - 1; i++) {
const currentFrame = frames[i]
const nextFrame = frames[i + 1]
// Add button to navigate to next frame
const button = figma.createFrame()
button.name = "Next Button"
button.resize(120, 40)
button.x = currentFrame.width - 140
button.y = currentFrame.height - 60
button.cornerRadius = 8
button.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.5, b: 1 } }]
button.layoutMode = 'HORIZONTAL'
button.primaryAxisAlignItems = 'CENTER'
button.counterAxisAlignItems = 'CENTER'
button.paddingLeft = 16
button.paddingRight = 16
const buttonText = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' })
buttonText.characters = 'Next'
buttonText.fontSize = 14
buttonText.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
button.appendChild(buttonText)
currentFrame.appendChild(button)
// Add reaction to navigate
await button.setReactionsAsync([
{
action: {
type: 'NODE',
destinationId: nextFrame.id,
navigation: 'NAVIGATE',
transition: {
type: 'SMART_ANIMATE',
easing: { type: 'EASE_IN_AND_OUT' },
duration: 0.3
},
preserveScrollPosition: false
},
trigger: {
type: 'ON_CLICK'
}
}
])
}
figma.notify(`Created navigation flow with ${frames.length} frames`)
}Example 20: Modal Overlay System
async function createModalSystem() {
// Create base screen
const baseScreen = figma.createFrame()
baseScreen.name = "Base Screen"
baseScreen.resize(375, 812)
baseScreen.fills = [{ type: 'SOLID', color: { r: 0.98, g: 0.98, b: 0.98 } }]
// Create open modal button
const openButton = figma.createFrame()
openButton.name = "Open Modal"
openButton.resize(200, 48)
openButton.x = (375 - 200) / 2
openButton.y = 400
openButton.cornerRadius = 24
openButton.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.5, b: 1 } }]
openButton.layoutMode = 'HORIZONTAL'
openButton.primaryAxisAlignItems = 'CENTER'
openButton.counterAxisAlignItems = 'CENTER'
const buttonText = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' })
buttonText.characters = 'Open Modal'
buttonText.fontSize = 16
buttonText.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
openButton.appendChild(buttonText)
baseScreen.appendChild(openButton)
// Create modal overlay
const modal = figma.createFrame()
modal.name = "Modal"
modal.resize(375, 812)
modal.fills = [{ type: 'SOLID', color: { r: 0, g: 0, b: 0, a: 0.5 } }]
modal.layoutMode = 'VERTICAL'
modal.primaryAxisAlignItems = 'CENTER'
modal.counterAxisAlignItems = 'CENTER'
// Modal content
const modalContent = figma.createFrame()
modalContent.name = "Modal Content"
modalContent.resize(320, 400)
modalContent.cornerRadius = 16
modalContent.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
modalContent.layoutMode = 'VERTICAL'
modalContent.itemSpacing = 16
modalContent.paddingLeft = 24
modalContent.paddingRight = 24
modalContent.paddingTop = 24
modalContent.paddingBottom = 24
const modalTitle = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
modalTitle.characters = 'Modal Title'
modalTitle.fontSize = 24
modalContent.appendChild(modalTitle)
const modalBody = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })
modalBody.characters = 'This is a modal overlay with content.'
modalBody.fontSize = 16
modalContent.appendChild(modalBody)
// Close button
const closeButton = figma.createFrame()
closeButton.name = "Close"
closeButton.resize(280, 48)
closeButton.cornerRadius = 8
closeButton.fills = [{ type: 'SOLID', color: { r: 0.9, g: 0.9, b: 0.9 } }]
closeButton.layoutMode = 'HORIZONTAL'
closeButton.primaryAxisAlignItems = 'CENTER'
closeButton.counterAxisAlignItems = 'CENTER'
const closeText = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' })
closeText.characters = 'Close'
closeText.fontSize = 16
closeButton.appendChild(closeText)
modalContent.appendChild(closeButton)
modal.appendChild(modalContent)
// Set up interactions
await openButton.setReactionsAsync([
{
action: {
type: 'NODE',
destinationId: modal.id,
navigation: 'OVERLAY',
transition: {
type: 'DISSOLVE',
easing: { type: 'EASE_OUT' },
duration: 0.2
},
overlayRelativePosition: { x: 0, y: 0 }
},
trigger: {
type: 'ON_CLICK'
}
}
])
await closeButton.setReactionsAsync([
{
action: {
type: 'CLOSE',
transition: {
type: 'DISSOLVE',
easing: { type: 'EASE_IN' },
duration: 0.2
}
},
trigger: {
type: 'ON_CLICK'
}
}
])
figma.notify('Modal system created!')
return { baseScreen, modal }
}---
Advanced Patterns
Example 21: Responsive Layout Converter
async function convertToResponsive(node: FrameNode) {
if (node.type !== 'FRAME') {
figma.notify('Please select a frame', { error: true })
return
}
// Store original child positions
const childData = node.children.map(child => ({
node: child,
x: 'x' in child ? child.x : 0,
y: 'y' in child ? child.y : 0,
width: 'width' in child ? child.width : 0,
height: 'height' in child ? child.height : 0
}))
// Convert to auto layout
node.layoutMode = 'VERTICAL'
node.primaryAxisSizingMode = 'AUTO'
node.counterAxisSizingMode = 'FIXED'
node.itemSpacing = 16
node.paddingLeft = 24
node.paddingRight = 24
node.paddingTop = 24
node.paddingBottom = 24
// Configure children
node.children.forEach((child, index) => {
if ('layoutAlign' in child) {
child.layoutAlign = 'STRETCH'
child.layoutGrow = 0
}
if ('minWidth' in child) {
child.minWidth = Math.min(childData[index].width, 200)
child.maxWidth = Math.max(childData[index].width, 800)
}
if ('minHeight' in child) {
child.minHeight = childData[index].height
}
})
figma.notify('Converted to responsive auto layout!')
}
// Usage
const selectedFrame = figma.currentPage.selection[0] as FrameNode
await convertToResponsive(selectedFrame)Example 22: Smart Component Swapper
async function smartSwapComponents(
findComponent: ComponentNode,
replaceComponent: ComponentNode
) {
const instances = figma.currentPage.findAllWithCriteria({
types: ['INSTANCE']
}).filter(instance => instance.mainComponent?.id === findComponent.id)
let swapped = 0
const errors: string[] = []
for (const instance of instances) {
try {
// Preserve overrides
const overrides = { ...instance.componentProperties }
// Swap component
await instance.swapAsync(replaceComponent)
// Reapply compatible overrides
for (const [key, value] of Object.entries(overrides)) {
if (key in instance.componentProperties) {
instance.setProperties({ [key]: value })
}
}
swapped++
} catch (error) {
errors.push(`Failed to swap ${instance.name}: ${error.message}`)
}
}
if (errors.length > 0) {
console.error('Swap errors:', errors)
}
figma.notify(`Swapped ${swapped} of ${instances.length} instances`)
return { swapped, total: instances.length, errors }
}---
Summary
This examples document includes 22 comprehensive, production-ready examples covering:
1-3: Component creation (button variants, icons, cards) 4-6: Auto layout patterns (navigation, grids, dashboards) 7-9: Design systems (tokens, typography, colors) 10-12: Plugin development (inspector, batch rename, style applier) 13-15: Data management (versioning, status tracking, analytics) 16-18: Export workflows (multi-format, icons, screenshots) 19-20: Prototyping (interactions, modals) 21-22: Advanced patterns (responsive conversion, component swapping)
All examples use Context7-validated Figma Plugin API patterns and best practices for production use.
Figma Design Skill
Production-ready Figma plugin development, component systems, auto layout, and design system management based on official Figma Plugin API documentation.
Overview
This skill provides comprehensive knowledge for working with Figma, covering:
- Plugin development with the Figma Plugin API
- Component architecture and design systems
- Auto layout and responsive design
- Variables and design tokens
- Prototyping and interactions
- Batch operations and automation
- Export workflows and image handling
Quick Start
Creating Your First Plugin
1. Set up plugin files:
my-plugin/
├── manifest.json
├── code.ts
└── ui.html (optional)2. manifest.json:
{
"name": "My First Plugin",
"id": "my-unique-id",
"api": "1.0.0",
"main": "code.js",
"ui": "ui.html",
"editorType": ["figma"]
}3. code.ts:
// Show UI
figma.showUI(__html__, { width: 400, height: 300 })
// Handle messages from UI
figma.ui.onmessage = async (msg) => {
if (msg.type === 'create-rectangle') {
const rect = figma.createRectangle()
rect.resize(100, 100)
rect.fills = [{
type: 'SOLID',
color: { r: 0.2, g: 0.5, b: 1 }
}]
figma.currentPage.selection = [rect]
figma.viewport.scrollAndZoomIntoView([rect])
}
if (msg.type === 'close') {
figma.closePlugin()
}
}4. ui.html:
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 16px;
font-family: 'Inter', sans-serif;
background: var(--figma-color-bg);
color: var(--figma-color-text);
}
button {
background: var(--figma-color-bg-brand);
color: var(--figma-color-text-onbrand);
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
}
</style>
</head>
<body>
<button onclick="create()">Create Rectangle</button>
<button onclick="close()">Close</button>
<script>
function create() {
parent.postMessage({
pluginMessage: { type: 'create-rectangle' }
}, '*')
}
function close() {
parent.postMessage({
pluginMessage: { type: 'close' }
}, '*')
}
</script>
</body>
</html>5. Build and test:
# Install dependencies
npm install @figma/plugin-typings --save-dev
# Compile TypeScript
tsc code.ts
# In Figma: Plugins > Development > Import plugin from manifestCore Workflows
1. Creating Components
// Create a button component
const button = figma.createComponent()
button.name = "Primary Button"
button.resize(120, 40)
// Add background
const bg = figma.createRectangle()
bg.resize(120, 40)
bg.cornerRadius = 8
bg.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.5, b: 1 } }]
button.appendChild(bg)
// Add text
const text = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' })
text.characters = 'Button'
text.fontSize = 14
text.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
text.x = (120 - text.width) / 2
text.y = (40 - text.height) / 2
button.appendChild(text)
// Create instance
const instance = button.createInstance()
instance.x = 200
instance.y = 1002. Auto Layout
// Create vertical auto-layout frame
const frame = figma.createFrame()
frame.name = "Card"
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'FIXED'
frame.resize(300, 0)
frame.itemSpacing = 16
frame.paddingLeft = 24
frame.paddingRight = 24
frame.paddingTop = 24
frame.paddingBottom = 24
frame.cornerRadius = 12
frame.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }]
// Add header
const header = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
header.characters = 'Card Title'
header.fontSize = 20
frame.appendChild(header)
// Add body
const body = figma.createText()
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })
body.characters = 'Card description goes here...'
body.fontSize = 14
frame.appendChild(body)
// Make body stretch
if ('layoutAlign' in body) {
body.layoutAlign = 'STRETCH'
}3. Design System with Variables
async function createDesignSystem() {
// Create collection
const tokens = figma.variables.createVariableCollection('Design Tokens')
const defaultMode = tokens.modes[0]
// Create color variables
const colors = {
'primary': { r: 0.2, g: 0.5, b: 1, a: 1 },
'secondary': { r: 0.5, g: 0.2, b: 0.8, a: 1 },
'background': { r: 1, g: 1, b: 1, a: 1 },
'text': { r: 0, g: 0, b: 0, a: 1 }
}
const colorVars = {}
for (const [name, value] of Object.entries(colors)) {
const variable = figma.variables.createVariable(
`color/${name}`,
tokens,
'COLOR'
)
variable.setValueForMode(defaultMode.modeId, value)
colorVars[name] = variable
}
// Create spacing variables
const spacings = [4, 8, 12, 16, 24, 32, 48, 64]
const spacingVars = []
for (let i = 0; i < spacings.length; i++) {
const variable = figma.variables.createVariable(
`spacing/${i}`,
tokens,
'FLOAT'
)
variable.setValueForMode(defaultMode.modeId, spacings[i])
spacingVars.push(variable)
}
// Add dark mode
const darkMode = tokens.addMode('Dark')
colorVars['primary'].setValueForMode(darkMode, {
r: 0.4, g: 0.7, b: 1, a: 1
})
colorVars['background'].setValueForMode(darkMode, {
r: 0.1, g: 0.1, b: 0.1, a: 1
})
colorVars['text'].setValueForMode(darkMode, {
r: 1, g: 1, b: 1, a: 1
})
figma.notify('Design system created!')
}4. Finding and Modifying Nodes
// Find all text nodes (FAST)
const textNodes = figma.currentPage.findAllWithCriteria({
types: ['TEXT']
})
// Update font size for all
for (const node of textNodes) {
await figma.loadFontAsync(node.fontName)
node.fontSize = 16
}
// Find nodes with specific plugin data
const approved = figma.currentPage.findAllWithCriteria({
pluginData: {
keys: ['status']
}
}).filter(node => node.getPluginData('status') === 'approved')
// Find frames and components
const containers = figma.currentPage.findAllWithCriteria({
types: ['FRAME', 'COMPONENT']
})5. Export Assets
async function exportAssets() {
const selection = figma.currentPage.selection
for (const node of selection) {
// Export 1x PNG
const png1x = await node.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 1 }
})
// Export 2x PNG
const png2x = await node.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 2 }
})
// Export SVG
const svg = await node.exportAsync({
format: 'SVG',
svgIdAttribute: true,
svgOutlineText: false
})
// Send to UI for download
figma.ui.postMessage({
type: 'export-ready',
name: node.name,
formats: {
'png1x': Array.from(png1x),
'png2x': Array.from(png2x),
'svg': Array.from(svg)
}
})
}
}Plugin Architecture Patterns
Event-Driven Architecture
class PluginController {
private listeners: Map<string, Function[]> = new Map()
constructor() {
this.setupFigmaListeners()
this.setupUIListeners()
}
setupFigmaListeners() {
figma.on('selectionchange', () => {
this.emit('selection-changed', figma.currentPage.selection)
})
figma.on('documentchange', (event) => {
this.emit('document-changed', event.documentChanges)
})
}
setupUIListeners() {
figma.ui.onmessage = (msg) => {
this.emit(msg.type, msg.data)
}
}
on(event: string, handler: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, [])
}
this.listeners.get(event)!.push(handler)
}
emit(event: string, data: any) {
const handlers = this.listeners.get(event) || []
handlers.forEach(handler => handler(data))
}
}
// Usage
const controller = new PluginController()
controller.on('selection-changed', (nodes) => {
console.log(`Selected ${nodes.length} nodes`)
figma.ui.postMessage({
type: 'selection-update',
count: nodes.length
})
})
controller.on('create-shapes', async (data) => {
// Handle shape creation
})State Management
interface PluginState {
preferences: {
theme: 'light' | 'dark'
lastColor: RGB
history: string[]
}
currentOperation: string | null
isProcessing: boolean
}
class StateManager {
private state: PluginState
private listeners: Set<(state: PluginState) => void> = new Set()
constructor() {
this.state = {
preferences: {
theme: 'light',
lastColor: { r: 0.2, g: 0.5, b: 1 },
history: []
},
currentOperation: null,
isProcessing: false
}
}
async load() {
const saved = await figma.clientStorage.getAsync('state')
if (saved) {
this.state = { ...this.state, ...saved }
}
}
async save() {
await figma.clientStorage.setAsync('state', this.state.preferences)
}
getState(): PluginState {
return { ...this.state }
}
setState(partial: Partial<PluginState>) {
this.state = { ...this.state, ...partial }
this.notifyListeners()
this.save()
}
subscribe(listener: (state: PluginState) => void) {
this.listeners.add(listener)
return () => this.listeners.delete(listener)
}
private notifyListeners() {
this.listeners.forEach(listener => listener(this.state))
}
}
// Usage
const stateManager = new StateManager()
await stateManager.load()
stateManager.subscribe((state) => {
figma.ui.postMessage({
type: 'state-update',
state
})
})Command Pattern
interface Command {
execute(): Promise<void>
undo(): Promise<void>
}
class CreateRectangleCommand implements Command {
private rectangle: RectangleNode | null = null
constructor(
private x: number,
private y: number,
private width: number,
private height: number
) {}
async execute() {
this.rectangle = figma.createRectangle()
this.rectangle.x = this.x
this.rectangle.y = this.y
this.rectangle.resize(this.width, this.height)
}
async undo() {
if (this.rectangle) {
this.rectangle.remove()
}
}
}
class CommandManager {
private history: Command[] = []
private currentIndex = -1
async execute(command: Command) {
// Remove any commands after current index
this.history = this.history.slice(0, this.currentIndex + 1)
await command.execute()
this.history.push(command)
this.currentIndex++
}
async undo() {
if (this.currentIndex >= 0) {
await this.history[this.currentIndex].undo()
this.currentIndex--
}
}
async redo() {
if (this.currentIndex < this.history.length - 1) {
this.currentIndex++
await this.history[this.currentIndex].execute()
}
}
}Performance Best Practices
1. Use Optimized Search
// ✅ FAST: Use findAllWithCriteria
const textNodes = figma.currentPage.findAllWithCriteria({
types: ['TEXT']
})
// ❌ SLOW: Use findAll with callback
const textNodes = figma.currentPage.findAll(n => n.type === 'TEXT')2. Enable Performance Flags
// Skip invisible instance children for better performance
figma.skipInvisibleInstanceChildren = true3. Batch Operations
// ✅ Create all nodes, then update viewport once
const nodes = []
for (let i = 0; i < 100; i++) {
const rect = figma.createRectangle()
rect.x = i * 120
nodes.push(rect)
}
figma.viewport.scrollAndZoomIntoView(nodes)
// ❌ Update viewport for each node
for (let i = 0; i < 100; i++) {
const rect = figma.createRectangle()
figma.viewport.scrollAndZoomIntoView([rect]) // Slow!
}4. Async Iteration
async function processLargeDataset(nodes: SceneNode[]) {
for (let i = 0; i < nodes.length; i++) {
await processNode(nodes[i])
// Yield to UI every 100 items
if (i % 100 === 0) {
await new Promise(resolve => setTimeout(resolve, 0))
figma.ui.postMessage({
type: 'progress',
current: i,
total: nodes.length
})
}
}
}Common Patterns
Loading Indicator
// code.ts
async function longOperation() {
figma.ui.postMessage({ type: 'loading', show: true })
try {
// Perform operation
await processNodes()
figma.ui.postMessage({ type: 'loading', show: false })
figma.notify('Operation complete!')
} catch (error) {
figma.ui.postMessage({ type: 'loading', show: false })
figma.notify('Error: ' + error.message, { error: true })
}
}<!-- ui.html -->
<div id="loading" style="display: none;">
<div class="spinner"></div>
<p>Processing...</p>
</div>
<script>
window.onmessage = (event) => {
const msg = event.data.pluginMessage
if (msg.type === 'loading') {
document.getElementById('loading').style.display =
msg.show ? 'block' : 'none'
}
}
</script>Error Boundaries
class PluginError extends Error {
constructor(
message: string,
public userMessage: string
) {
super(message)
}
}
async function safeExecute<T>(
operation: () => Promise<T>,
errorMessage: string = 'Operation failed'
): Promise<T | null> {
try {
return await operation()
} catch (error) {
console.error(error)
if (error instanceof PluginError) {
figma.notify(error.userMessage, { error: true })
} else {
figma.notify(errorMessage, { error: true })
}
return null
}
}
// Usage
await safeExecute(
async () => {
const node = await figma.getNodeByIdAsync(id)
if (!node) {
throw new PluginError(
'Node not found',
'The selected node no longer exists'
)
}
return node
},
'Failed to find node'
)Validation
function validateSelection(): boolean {
const selection = figma.currentPage.selection
if (selection.length === 0) {
figma.notify('Please select at least one node', { error: true })
return false
}
const hasInvalidTypes = selection.some(
node => !['FRAME', 'COMPONENT'].includes(node.type)
)
if (hasInvalidTypes) {
figma.notify('Please select only frames or components', { error: true })
return false
}
return true
}
// Usage
if (!validateSelection()) {
return
}
// Proceed with operationTypeScript Configuration
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@figma"
]
},
"include": ["*.ts"],
"exclude": ["node_modules"]
}package.json:
{
"name": "my-figma-plugin",
"version": "1.0.0",
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
},
"devDependencies": {
"@figma/plugin-typings": "^1.89.0",
"typescript": "^5.0.0"
}
}Testing Strategies
Unit Testing Node Operations
// test-helpers.ts
export function createMockRectangle(
props: Partial<RectangleNode> = {}
): RectangleNode {
const rect = figma.createRectangle()
Object.assign(rect, props)
return rect
}
export function assertNodeProperties(
node: SceneNode,
expected: Record<string, any>
) {
for (const [key, value] of Object.entries(expected)) {
if (node[key] !== value) {
throw new Error(
`Expected ${key} to be ${value}, got ${node[key]}`
)
}
}
}
// Usage in plugin
try {
const rect = createMockRectangle({ x: 100, y: 100 })
assertNodeProperties(rect, { x: 100, y: 100 })
console.log('✓ Test passed')
} catch (error) {
console.error('✗ Test failed:', error.message)
}Integration Testing
async function testWorkflow() {
console.log('Starting integration test...')
// Test 1: Create component
const component = figma.createComponent()
component.name = 'Test Button'
console.assert(component.type === 'COMPONENT', 'Component created')
// Test 2: Create instance
const instance = component.createInstance()
console.assert(
instance.mainComponent?.id === component.id,
'Instance linked to component'
)
// Test 3: Export
const bytes = await component.exportAsync({ format: 'PNG' })
console.assert(bytes.length > 0, 'Export successful')
// Cleanup
component.remove()
instance.remove()
console.log('✓ All tests passed')
}Debugging Tips
1. Console Logging
// Log node hierarchy
function logNodeTree(node: BaseNode, indent = 0) {
console.log(' '.repeat(indent) + `${node.type}: ${node.name}`)
if ('children' in node) {
node.children.forEach(child => logNodeTree(child, indent + 1))
}
}
// Log selection details
figma.on('selectionchange', () => {
console.group('Selection Changed')
figma.currentPage.selection.forEach(node => {
console.log({
type: node.type,
name: node.name,
id: node.id,
position: { x: node.x, y: node.y },
size: 'width' in node ? { width: node.width, height: node.height } : null
})
})
console.groupEnd()
})2. Performance Profiling
async function profileOperation<T>(
name: string,
operation: () => Promise<T>
): Promise<T> {
const start = performance.now()
const result = await operation()
const duration = performance.now() - start
console.log(`${name} took ${duration.toFixed(2)}ms`)
return result
}
// Usage
await profileOperation('Find all text nodes', async () => {
return figma.currentPage.findAllWithCriteria({ types: ['TEXT'] })
})3. Error Tracking
const errors: Array<{ timestamp: Date; message: string; stack?: string }> = []
function trackError(error: Error) {
errors.push({
timestamp: new Date(),
message: error.message,
stack: error.stack
})
// Send to UI for display
figma.ui.postMessage({
type: 'error-logged',
error: {
message: error.message,
timestamp: new Date().toISOString()
}
})
}
// Global error handler
process.on('unhandledRejection', (error: Error) => {
trackError(error)
figma.notify('An unexpected error occurred', { error: true })
})Resources
- Official Documentation: https://www.figma.com/plugin-docs/
- Context7 Library: /figma/plugin-typings (Trust Score: 9.8)
- Plugin Samples: https://github.com/figma/plugin-samples
- TypeScript Typings: https://github.com/figma/plugin-typings
- Community Forum: https://forum.figma.com/
- Widget API: https://www.figma.com/widget-docs/
- REST API: https://www.figma.com/developers/api
Next Steps
1. Review SKILL.md for comprehensive API reference 2. Check EXAMPLES.md for 18+ practical examples 3. Build your first plugin following the Quick Start 4. Join the Figma community forum for support 5. Explore official plugin samples on GitHub
Support
For issues and questions:
- Figma Plugin Forum: https://forum.figma.com/c/plugin-api/
- GitHub Issues: https://github.com/figma/plugin-samples/issues
- Official Documentation: https://www.figma.com/plugin-docs/
Related skills
How it compares
Pick figma-design when automating Figma via the Plugin API; pair with Figma MCP when agents need live read-write canvas access outside plugin code.
FAQ
What does figma-design cover?
figma-design covers Figma Plugin API development, component systems, auto layout, prototyping reactions, variables, batch operations, and PNG, JPG, SVG export. The version 1.0.0 skill pulls from official Figma documentation via Context7.
How do you install figma-design?
Install figma-design with npx skills add https://github.com/manutej/luxor-claude-marketplace --skill figma-design. The skill lives in the luxor-design-toolkit plugin inside the luxor-claude-marketplace repository.