figma-design by manutej/luxor-claude-marketplace
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill figma-design基于 Context7 官方 Figma Plugin API 文档的 Figma 设计工作流程、插件开发、组件系统、自动布局、原型设计和设计系统管理的全面指南。
在以下场景中使用此技能:
Figma 文档结构是一个节点树:
DocumentNode (根节点)
└── PageNode
├── FrameNode
│ ├── TextNode
│ ├── RectangleNode
│ └── ComponentNode
└── SectionNode
└── FrameNode
关键节点类型:
FRAME:具有自动布局功能的容器广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
COMPONENT:可重用的设计元素(主组件)INSTANCE:组件的副本TEXT:可编辑的文本图层RECTANGLE、ELLIPSE、POLYGON、STAR、LINE:基本形状SECTION:用于框架的组织容器GROUP:非布局容器组件是可以多次实例化的主元素:
// 创建组件
const button = figma.createComponent()
button.name = "Primary Button"
button.resize(120, 40)
// 添加视觉元素
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)
// 创建实例
const buttonInstance = button.createInstance()
buttonInstance.x = 200
buttonInstance.y = 100
组件属性:
BOOLEAN:切换可见性或状态TEXT:可自定义的文本内容INSTANCE_SWAP:交换嵌套组件VARIANT:不同的组件变体自动布局创建响应式框架,以适应内容变化:
核心属性:
layoutMode:'HORIZONTAL'、'VERTICAL' 或 'NONE'primaryAxisSizingMode:'FIXED' 或 'AUTO'counterAxisSizingMode:'FIXED' 或 'AUTO'paddingLeft、paddingRight、paddingTop、paddingBottomitemSpacing:子元素之间的间距primaryAxisAlignItems:主轴上的对齐方式counterAxisAlignItems:交叉轴上的对齐方式子元素的约束:
minWidth、maxWidth:宽度边界
minHeight、maxHeight:高度边界
layoutAlign:'MIN'、'CENTER'、'MAX'、'STRETCH'
layoutGrow:0(固定)或 1(填充容器)
// 创建自动布局框架 const frame = figma.createFrame() 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
// 添加带有约束的子元素 const child = figma.createRectangle() child.resize(252, 100) child.layoutAlign = 'STRETCH' // 填充宽度 child.minHeight = 100 child.maxHeight = 200 frame.appendChild(child)
约束控制节点在其父节点变化时如何调整大小:
interface Constraints {
horizontal: 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'SCALE'
vertical: 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'SCALE'
}
node.constraints = {
horizontal: 'MIN', // 固定到左侧
vertical: 'MAX' // 固定到底部
}
约束类型:
MIN:固定到顶部/左侧边缘CENTER:在父元素中居中MAX:固定到底部/右侧边缘STRETCH:随父元素缩放(两个边缘)SCALE:保持比例位置和大小变量创建动态设计系统(仅限 Figma Design):
// 创建变量集合
const collection = figma.variables.createVariableCollection('Design Tokens')
// 创建颜色变量
const primaryColor = figma.variables.createVariable(
'color/primary',
collection,
'COLOR'
)
// 为默认模式设置值
const defaultMode = collection.modes[0]
primaryColor.setValueForMode(defaultMode.modeId, {
r: 0.2, g: 0.5, b: 1, a: 1
})
// 添加深色模式
const darkMode = collection.addMode('Dark')
primaryColor.setValueForMode(darkMode, {
r: 0.4, g: 0.7, b: 1, a: 1
})
// 创建变量别名(引用)
const accentColor = figma.variables.createVariable(
'color/accent',
collection,
'COLOR'
)
const alias = figma.variables.createVariableAlias(primaryColor)
accentColor.setValueForMode(defaultMode.modeId, alias)
// 将变量绑定到节点
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]
变量类型:
COLOR:RGB/RGBA 值FLOAT:数值(间距、尺寸)BOOLEAN:真/假标志STRING:文本值样式定义可重用的视觉属性:
// 绘制样式(填充/描边)
const paintStyle = figma.createPaintStyle()
paintStyle.name = 'Brand/Primary'
paintStyle.paints = [{
type: 'SOLID',
color: { r: 0.2, g: 0.5, b: 1 }
}]
// 文本样式
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' }
// 效果样式(阴影、模糊)
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'
}]
// 应用样式
rect.fillStyleId = paintStyle.id
text.textStyleId = textStyle.id
frame.effectStyleId = effectStyle.id
反应定义原型中的交互行为:
// 在节点上设置反应
await node.setReactionsAsync([
{
action: {
type: 'NODE',
destinationId: 'nodeId', // 目标框架
navigation: 'NAVIGATE',
transition: {
type: 'SMART_ANIMATE',
easing: { type: 'EASE_IN_AND_OUT' },
duration: 0.3
},
preserveScrollPosition: false
},
trigger: {
type: 'ON_CLICK'
}
}
])
触发器类型:
ON_CLICK:点击/轻触ON_HOVER:鼠标悬停ON_PRESS:触摸按压ON_DRAG:拖拽交互MOUSE_ENTER、MOUSE_LEAVE、MOUSE_UP、MOUSE_DOWNAFTER_TIMEOUT:延迟触发器导航类型:
NAVIGATE:前往目的地SWAP:交换覆盖层OVERLAY:作为覆盖层打开SCROLL_TO:滚动到位置CHANGE_TO:更改为状态控制框架如何作为覆盖层显示:
frame.overlayPositionType // 'CENTER' | 'TOP_LEFT' | 'TOP_CENTER' | 等
frame.overlayBackground // 覆盖层如何遮挡背景
frame.overlayBackgroundInteraction // 点击穿透行为
配置框架滚动行为:
frame.overflowDirection = 'VERTICAL_SCROLLING' // 或 'HORIZONTAL_SCROLLING'
frame.numberOfFixedChildren = 1 // 前 N 个子元素在滚动时保持固定
基本插件文件:
my-plugin/
├── manifest.json # 插件配置
├── code.ts # 主要插件逻辑
└── ui.html # 可选 UI
manifest.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"]
}
}
// 初始化插件
async function init() {
// 显示 UI(可选)
figma.showUI(__html__, {
width: 400,
height: 500,
title: "My Plugin",
themeColors: true
})
// 加载保存的偏好设置
const prefs = await figma.clientStorage.getAsync('preferences')
// 发送初始数据到 UI
figma.ui.postMessage({
type: 'init',
data: prefs
})
}
// 处理 UI 消息
figma.ui.onmessage = async (msg) => {
if (msg.type === 'create-shapes') {
await createShapes(msg.count, msg.color)
}
if (msg.type === 'export') {
await exportSelection()
}
}
// 关闭时清理
figma.on('close', () => {
console.log('Plugin closing...')
})
// 启动插件
init()
从插件发送消息到 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
}))
})
在 UI 中接收消息(ui.html):
<script>
window.onmessage = (event) => {
const msg = event.data.pluginMessage
if (msg.type === 'selection-changed') {
document.getElementById('count').textContent = msg.count
}
}
// 发送消息到插件
function createShapes() {
parent.postMessage({
pluginMessage: {
type: 'create-shapes',
count: 5,
color: { r: 1, g: 0, b: 0 }
}
}, '*')
}
</script>
监控文档和选择变化:
// 选择变化
figma.on('selectionchange', () => {
const selection = figma.currentPage.selection
console.log(`Selected ${selection.length} nodes`)
})
// 文档变化
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(', ')}`)
}
}
})
// 页面变化
figma.on('currentpagechange', () => {
console.log(`Now on page: ${figma.currentPage.name}`)
})
插件数据(对您的插件私有):
// 在节点上存储数据
node.setPluginData('status', 'approved')
node.setPluginData('metadata', JSON.stringify({
author: 'John',
tags: ['important']
}))
// 检索数据
const status = node.getPluginData('status')
const metadata = JSON.parse(node.getPluginData('metadata') || '{}')
// 列出所有键
const keys = node.getPluginDataKeys()
共享插件数据(可通过命名空间访问):
// 存储共享数据
node.setSharedPluginData('com.example.plugin', 'version', '2.0')
// 检索共享数据
const version = node.getSharedPluginData('com.example.plugin', 'version')
// 列出共享键
const keys = node.getSharedPluginDataKeys('com.example.plugin')
客户端存储(持久化设置):
// 保存偏好设置
await figma.clientStorage.setAsync('preferences', {
theme: 'dark',
lastUsed: new Date().toISOString()
})
// 加载偏好设置
const prefs = await figma.clientStorage.getAsync('preferences')
// 删除数据
await figma.clientStorage.deleteAsync('preferences')
// 列出所有键
const keys = await figma.clientStorage.keysAsync()
对于大型文档最快的方法(快数百倍):
// 启用性能优化
figma.skipInvisibleInstanceChildren = true
// 按类型查找
const textNodes = figma.currentPage.findAllWithCriteria({
types: ['TEXT']
})
// 查找多种类型
const shapes = figma.currentPage.findAllWithCriteria({
types: ['RECTANGLE', 'ELLIPSE', 'POLYGON']
})
// 查找带有插件数据的节点
const nodesWithStatus = figma.currentPage.findAllWithCriteria({
pluginData: {
keys: ['status']
}
})
// 查找带有共享插件数据的节点
const nodesWithSharedData = figma.currentPage.findAllWithCriteria({
sharedPluginData: {
namespace: 'com.example.plugin',
keys: ['version']
}
})
// 组合条件
const textWithData = figma.currentPage.findAllWithCriteria({
types: ['TEXT'],
pluginData: {} // 任何插件数据
})
// 查找所有匹配的节点
const frames = figma.currentPage.findAll(node => node.type === 'FRAME')
// 查找第一个匹配项
const template = figma.currentPage.findOne(node =>
node.name.startsWith('Template')
)
// 按 ID 查找
const node = await figma.getNodeByIdAsync('123:456')
// 递归树遍历
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)
// 导出为 PNG
const pngBytes = await node.exportAsync({
format: 'PNG',
constraint: { type: 'SCALE', value: 2 } // 2x 分辨率
})
// 导出为 JPG
const jpgBytes = await node.exportAsync({
format: 'JPG',
constraint: { type: 'SCALE', value: 1 },
contentsOnly: false // 包含背景
})
// 导出为 SVG
const svgBytes = await node.exportAsync({
format: 'SVG',
svgIdAttribute: true,
svgOutlineText: false,
svgSimplifyStroke: true
})
// 使用固定尺寸导出
const thumbnail = await node.exportAsync({
format: 'PNG',
constraint: { type: 'WIDTH', value: 200 }
})
// 使用高度约束导出
const preview = await node.exportAsync({
format: 'PNG',
constraint: { type: 'HEIGHT', value: 400 }
})
// 从 URL 加载
const image = await figma.createImageAsync('https://example.com/image.png')
const { width, height } = await image.getSizeAsync()
// 创建带有图像的矩形
const rect = figma.createRectangle()
rect.resize(width, height)
rect.fills = [{
type: 'IMAGE',
imageHash: image.hash,
scaleMode: 'FILL' // 或 'FIT'、'CROP'、'TILE'
}]
// 从字节加载
function loadFromBytes(bytes: Uint8Array) {
const image = figma.createImage(bytes)
return image
}
// 从 SVG 字符串创建
const svgString = `
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" fill="#3366ff"/>
</svg>
`
const node = figma.createNodeFromSvg(svgString)
// 创建文本(必须先加载字体)
const text = figma.createText()
await figma.loadFontAsync(text.fontName) // 加载默认字体
// 设置内容
text.characters = 'Hello World'
// 更改字体
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
text.fontName = { family: 'Inter', style: 'Bold' }
// 样式设置
text.fontSize = 24
text.lineHeight = { value: 150, unit: 'PERCENT' }
text.letterSpacing = { value: 0, unit: 'PIXELS' }
text.textAlignHorizontal = 'CENTER'
text.textAlignVertical = 'CENTER'
text.textCase = 'UPPER' // 或 'LOWER'、'TITLE'、'ORIGINAL'
text.textDecoration = 'UNDERLINE' // 或 'STRIKETHROUGH'、'NONE'
// 对范围应用格式化
text.setRangeFontSize(0, 5, 32) // 前 5 个字符 = 32px
text.setRangeFills(0, 5, [{
type: 'SOLID',
color: { r: 1, g: 0, b: 0 }
}])
// 获取位置处的字体
const fontName = text.getRangeFontName(0, 1)
const fontSize = text.getRangeFontSize(0, 1)
// 应用约束调整大小
node.resize(300, 200)
// 不应用约束调整大小
node.resizeWithoutConstraints(300, 200)
// 按比例缩放
node.rescale(1.5) // 150% 缩放
// 位置
node.x = 100
node.y = 200
// 相对于父元素
console.log(node.relativeTransform) // [[1,0,x], [0,1,y]]
// 页面上的绝对位置
console.log(node.absoluteTransform)
// 边界框
const bounds = node.absoluteBoundingBox
// { x: number, y: number, width: number, height: number }
虽然插件无法通过 API 创建评论,但它们可以:
// 插件数据可以跟踪自定义版本
node.setPluginData('version', '2.1.0')
node.setPluginData('lastModified', new Date().toISOString())
node.setPluginData('modifiedBy', 'user@example.com')
// 检查节点是否来自外部库
if (node.type === 'INSTANCE') {
const component = node.mainComponent
if (component && component.parent?.type === 'PAGE') {
// 组件来自库
}
}
// ✅ 对于大型文档使用 findAllWithCriteria
const nodes = figma.currentPage.findAllWithCriteria({
types: ['TEXT']
})
// ❌ 避免对简单的类型搜索使用 findAll
const nodes = figma.currentPage.findAll(n => n.type === 'TEXT')
// ✅ 启用不可见实例优化
figma.skipInvisibleInstanceChildren = true
// ✅ 批量操作
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)
// ❌ 避免单独的视口更新
for (let i = 0; i < 100; i++) {
const rect = figma.createRectangle()
figma.viewport.scrollAndZoomIntoView([rect]) // 慢!
}
// ✅ 在修改文本前始终加载字体
const text = figma.createText()
await figma.loadFontAsync(text.fontName)
text.characters = 'Hello'
// ✅ 在更改 fontName 前加载字体
await figma.loadFontAsync({ family: 'Roboto', style: 'Bold' })
text.fontName = { family: 'Roboto', style: 'Bold' }
// ❌ 这会出错
text.characters = 'Hello' // 错误:字体未加载
try {
const node = await figma.getNodeByIdAsync(id)
if (!node) {
figma.notify('Node not found', { error: true })
return
}
// 处理节点
} catch (error) {
figma.notify(`Error: ${error.message}`, { error: true })
console.error(error)
}
// ✅ 清理大型数据
figma.on('close', () => {
// 清除大型缓存
cache.clear()
})
// ✅ 对大型数据集使用异步迭代
async function processManyNodes() {
const nodes = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] })
for (let i = 0; i < nodes.length; i++) {
await processNode(nodes[i])
// 每 100 个项目让出 UI 控制权
if (i % 100 === 0) {
await new Promise(resolve => setTimeout(resolve, 0))
}
}
}
// ✅ 类型守卫
if (node.type === 'TEXT') {
// TypeScript 知道 node 是 TextNode
console.log(node.characters)
}
if ('children' in node) {
// 节点有子元素
node.children.forEach(child => console.log(child.name))
}
// ✅ 使用特定类型
function processText(node: TextNode) {
console.log(node.characters)
}
// ❌ 避免使用 any
function processNode(node: any) { // 不好!
console.log(node.characters) // 可能出错
}
async function setupDesignSystem() {
// 创建变量集合
const tokens = figma.variables.createVariableCollection('Design Tokens')
// 颜色
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)
}
// 间距
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)
})
// 创建样式
const primaryStyle = figma.createPaintStyle()
primaryStyle.name = 'Color/Primary'
primaryStyle.paints = [{
type: 'SOLID',
color: colors['color/primary']
}]
figma.notify('Design system created!')
}
async function createButtonLibrary() {
// 为变体创建组件集
const buttons = figma.createComponentSet()
buttons.name = "Button"
// 主要变体
const primary = figma.createComponent()
primary.name = "Type=Primary, Size=Medium"
primary.resize(120, 40)
buttons.appendChild(primary)
// 次要变体
const secondary = figma.createComponent()
secondary.name = "Type=Secondary, Size=Medium"
secondary.resize(120, 40)
buttons.appendChild(secondary)
// 大尺寸
const large = figma.createComponent()
large.name = "Type=Primary, Size=Large"
large.resize(160, 48)
buttons.appendChild(large)
return buttons
}
async function exportAllFrames() {
const frames = figma.currentPage.findAllWithCriteria({
types: ['FRAME']
})
const exports = []
for (const frame of frames) {
// 导出 1x 和 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
})
}
// 发送到 UI 以下载
figma.ui.postMessage({
type: 'exports-ready',
exports
})
}
async function syncStyles() {
const paintStyles = await figma.getLocalPaintStylesAsync()
const textStyles = await figma.getLocalTextStylesAsync()
// 更新所有实例
const nodes = figma.currentPage.findAllWithCriteria({
types: ['RECTANGLE', 'TEXT']
})
for (const node of nodes) {
if (node.type === 'RECTANGLE') {
// 根据名称应用绘制样式
const style = paintStyles.find(s => s.name === 'Brand/Primary')
if (style) {
node.fillStyleId = style.id
}
}
if (node.type === 'TEXT') {
// 应用文本样式
const style = textStyles.find(s => s.name === 'Body/Regular')
if (style) {
node.textStyleId = style.id
}
}
}
figma.notify('Styles synced!')
}
<!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>
<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>
if (figma.editorType === 'figjam') {
// 创建便利贴
const sticky = figma.createSticky()
sticky.x = 100
sticky.y = 100
await figma.loadFontAsync({ family: "Roboto", style: "Regular" })
sticky.text.characters = "TODO: Review designs"
// 创建连接器
const connector = figma.createConnector()
connector.connectorStart = {
endpointNodeId: sticky.id,
position: { x: 0, y: 0.5 }
}
// 创建形状
const shape = figma.createShape()
shape.resize(100, 100)
shape.fills = [{ type: 'SOLID', color: { r: 1, g: 0.8, b: 0 } }]
}
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`)
}
function convertToAutoLayout(frame: FrameNode) {
// 存储原始位置
const childData = frame.children.map(child => ({
node: child,
x: child.x,
y: child.y
}))
// 启用自动布局
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'FIXED'
frame.itemSpacing = 16
frame.paddingLeft = 24
frame.paddingRight = 24
frame.paddingTop = 24
frame.paddingBottom = 24
// 配置子元素
frame.children.forEach(child => {
if ('layoutAlign' in child) {
child.layoutAlign = 'STRETCH'
}
})
}
function makeResponsive(frame: FrameNode) {
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'FIXED'
// 使子元素响应式
frame.children.forEach(child => {
if ('resize' in child) {
child.layoutAlign = 'STRETCH'
child.layoutGrow = 0
child.minWidth = 200
child.maxWidth = 800
}
})
}
// 控制台日志
console.log('Selection:', figma.currentPage.selection)
console.error('Error occurred:', error)
// 通知
figma.notify('Success!', { timeout: 2000 })
figma.notify('Error occurred', { error: true })
// 调试助手
function debugNode(node: SceneNode) {
console.log('Node Debug Info:')
console.log(' Type:', node.type)
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.
Use this skill when working with:
The Figma document structure is a tree of nodes:
DocumentNode (root)
└── PageNode
├── FrameNode
│ ├── TextNode
│ ├── RectangleNode
│ └── ComponentNode
└── SectionNode
└── FrameNode
Key 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 containerComponents 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 = 100
Component Properties:
BOOLEAN: Toggle visibility or statesTEXT: Customizable text contentINSTANCE_SWAP: Swap nested componentsVARIANT: Different component variationsAuto 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 axisConstraints for Children:
minWidth, maxWidth: Width boundaries
minHeight, maxHeight: Height boundaries
layoutAlign: '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)
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 sizeVariables 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 valuesStyles 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.id
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 triggerNavigation Types:
NAVIGATE: Go to destinationSWAP: Swap overlayOVERLAY: Open as overlaySCROLL_TO: Scroll to positionCHANGE_TO: Change to stateControl how frames appear as overlays:
frame.overlayPositionType // 'CENTER' | 'TOP_LEFT' | 'TOP_CENTER' | etc.
frame.overlayBackground // How overlay obscures background
frame.overlayBackgroundInteraction // Click-through behavior
Configure frame scrolling behavior:
frame.overflowDirection = 'VERTICAL_SCROLLING' // or 'HORIZONTAL_SCROLLING'
frame.numberOfFixedChildren = 1 // First N children stay fixed during scroll
Basic Plugin Files:
my-plugin/
├── manifest.json # Plugin configuration
├── code.ts # Main plugin logic
└── ui.html # Optional UI
manifest.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"]
}
}
// 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()
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>
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}`)
})
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()
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
})
// 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 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 }
})
// 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)
// 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'
// 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)
// Resize with constraints applied
node.resize(300, 200)
// Resize without constraints
node.resizeWithoutConstraints(300, 200)
// Scale proportionally
node.rescale(1.5) // 150% scale
// 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 }
While plugins can't create comments via API, they can:
// Plugin data can track custom versioning
node.setPluginData('version', '2.1.0')
node.setPluginData('lastModified', new Date().toISOString())
node.setPluginData('modifiedBy', 'user@example.com')
// 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
}
}
// ✅ 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!
}
// ✅ 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 loaded
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)
}
// ✅ 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))
}
}
}
// ✅ 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
}
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!')
}
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
}
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
})
}
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!')
}
<!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>
<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>
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 } }]
}
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`)
}
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'
}
})
}
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
}
})
}
// 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)
}
}
This skill covers:
Use this skill for building production-ready Figma plugins, automating design workflows, managing design systems, and creating scalable component libraries.
Weekly Installs
109
Repository
GitHub Stars
44
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
cursor91
codex91
opencode90
gemini-cli89
github-copilot85
claude-code78
shadcn/ui 框架:React 组件库与 UI 设计系统,Tailwind CSS 最佳实践
69,400 周安装
Shopify Polaris Web Components 使用指南:为 App Home 构建 UI 的完整教程
123 周安装
TypeScript 严格模式配置、项目结构与工具链最佳实践指南
123 周安装
Django REST API开发指南:使用DRF构建可扩展、高性能的API接口
128 周安装
React Native 最佳实践指南 | Vercel Labs 性能优化与代码规范
76 周安装
Akka.NET 测试模式指南:单元测试、持久化演员与集群分片测试
124 周安装
Ralph Wiggum 循环模式:Claude Code 自动化迭代开发工具,实现代码重构与测试驱动开发
124 周安装