- name
- figma-console-mcp-skills
- description
- Use powerful Figma Console MCP capabilities as Markdown skills for design tokens, variables, components, WCAG lint, a11y audits, version history, FigJam & Slides
- triggers
- ["export figma design tokens to code","audit figma file accessibility","generate component documentation from figma","import tokens into figma variables","analyze figma component variants","create figma version changelog","lint figma design for wcag compliance","manage figma variables programmatically"]
# Figma Console MCP Skills
> Skill by [ara.so](https://ara.so) — Design Skills collection.
A comprehensive collection of 22 self-contained skills that extend the native Figma MCP server with design-systems workflows: design token export/import (DTCG, CSS, Tailwind), variable management, component analysis, WCAG linting, accessibility audits, version history, and FigJam/Slides authoring. Each skill is a Markdown playbook with ready-to-paste JavaScript for the `use_figma` tool.
## Installation
**Prerequisites:**
- Native Figma MCP server configured with OAuth
- The official `figma-use` skill (provides Figma Plugin API reference)
- For 4 REST skills only: Figma personal access token in `$FIGMA_TOKEN`
**Option 1: Clone all skills**
```bash
git clone https://github.com/PercentProduction/figma-console-mcp-skills-347.git
cd figma-console-mcp-skills-347
npm install
npm start
```
**Option 2: Copy individual skills**
```bash
# Each skill folder is self-contained
cp -R figma-console-mcp-skills-347/figma-export-tokens ~/.claude/skills/
cp -R figma-console-mcp-skills-347/figma-lint-design ~/.claude/skills/
```
**Option 3: Claude Desktop/Web (no terminal)**
- Compress individual skill folders (e.g., `figma-export-tokens.zip`)
- Upload via **Create / upload a skill** in Claude
- Toggle skill on
## Core Concepts
### Skill Structure
Each skill is a folder containing:
- `SKILL.md` — playbook with YAML frontmatter
- `scripts/*.js` — ready-to-paste snippets for `use_figma`
- `scripts/*.mjs` / `*.sh` — Node/bash for REST skills
- `references/` — skill-specific documentation (self-contained)
### use_figma Conventions
Scripts follow these patterns:
```javascript
// Top-level await supported
const nodes = await figma.currentPage.findAll(n => n.type === 'FRAME');
// Inline inputs (no external deps)
const collectionName = "Brand Tokens";
// Return for output
return {
nodeCount: nodes.length,
data: nodes.map(n => ({ id: n.id, name: n.name }))
};
```
## Key Skills Overview
### 🎨 Tokens & Variables
#### figma-export-tokens
Export Figma variables to code-ready formats (DTCG, CSS, Tailwind, SCSS, TypeScript).
**Use case:** Export design tokens for consumption in code
**Example: Export to DTCG JSON**
```javascript
// In use_figma tool
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const collection = collections.find(c => c.name === "Semantic Tokens");
const modes = collection.modes;
const variables = await Promise.all(
collection.variableIds.map(id => figma.variables.getVariableByIdAsync(id))
);
// Resolve aliases recursively
async function resolveValue(value) {
if (value.type === 'VARIABLE_ALIAS') {
const aliasVar = await figma.variables.getVariableByIdAsync(value.id);
return resolveValue(aliasVar.valuesByMode[Object.keys(aliasVar.valuesByMode)[0]]);
}
return value;
}
const dtcg = {};
for (const variable of variables) {
for (const mode of modes) {
const rawValue = variable.valuesByMode[mode.modeId];
const resolved = await resolveValue(rawValue);
dtcg[variable.name] = {
$type: variable.resolvedType.toLowerCase(),
$value: resolved,
$description: variable.description || undefined
};
}
}
return JSON.stringify(dtcg, null, 2);
```
**Example: Export to CSS Custom Properties**
```javascript
const collections = await figma.variables.getLocalVariableCollectionsAsync();
const vars = [];
for (const collection of collections) {
const variables = await Promise.all(
collection.variableIds.map(id => figma.variables.getVariableByIdAsync(id))
);
for (const variable of variables) {
const value = variable.valuesByMode[Object.keys(variable.valuesByMode)[0]];
const cssName = `--${variable.name.toLowerCase().replace(/\s+/g, '-')}`;
if (typeof value === 'object' && value.r !== undefined) {
// Color
const r = Math.round(value.r * 255);
const g = Math.round(value.g * 255);
const b = Math.round(value.b * 255);
vars.push(`${cssName}: rgb(${r}, ${g}, ${b});`);
} else if (typeof value === 'number') {
vars.push(`${cssName}: ${value}px;`);
} else {
vars.push(`${cssName}: ${value};`);
}
}
}
return `:root {\n ${vars.join('\n ')}\n}`;
```
#### figma-import-tokens
Push tokens (DTCG/JSON) into Figma as variables.
**Use case:** Sync tokens from code back into Figma
**Example: Import DTCG JSON**
```javascript
// Input: dtcgJson (string)
const tokens = JSON.parse(dtcgJson);
let collection = await figma.variables.getLocalVariableCollectionsAsync()
.then(cols => cols.find(c => c.name === "Imported Tokens"));
if (!collection) {
collection = figma.variables.createVariableCollection("Imported Tokens");
}
for (const [name, token] of Object.entries(tokens)) {
const existing = await figma.variables.getLocalVariablesAsync()
.then(vars => vars.find(v => v.name === name));
let variable;
if (existing) {
variable = existing;
} else {
const resolvedType = token.$type === 'color' ? 'COLOR' : 'FLOAT';
variable = figma.variables.createVariable(name, collection.id, resolvedType);
}
const modeId = collection.modes[0].modeId;
if (token.$type === 'color') {
const hex = token.$value;
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
variable.setValueForMode(modeId, { r, g, b });
} else {
variable.setValueForMode(modeId, parseFloat(token.$value));
}
if (token.$description) {
variable.description = token.$description;
}
}
return { imported: Object.keys(tokens).length };
```
#### figma-manage-variables
CRUD operations for variables: create, update, delete, batch operations, scopes.
**Example: Batch create variables**
```javascript
const specs = [
{ name: "spacing/sm", value: 8, type: "FLOAT" },
{ name: "spacing/md", value: 16, type: "FLOAT" },
{ name: "spacing/lg", value: 24, type: "FLOAT" }
];
const collection = figma.variables.createVariableCollection("Spacing");
const modeId = collection.modes[0].modeId;
for (const spec of specs) {
const variable = figma.variables.createVariable(spec.name, collection.id, spec.type);
variable.setValueForMode(modeId, spec.value);
variable.scopes = ['ALL_SCOPES'];
}
return { created: specs.length, collectionId: collection.id };
```
### 🧩 Components & Design System
#### figma-analyze-component-set
Extract variant state machine, CSS pseudo-class mappings, visual diffs.
**Example: Extract variant states**
```javascript
const componentSet = figma.currentPage.selection[0];
if (componentSet.type !== 'COMPONENT_SET') {
throw new Error('Select a component set');
}
const variants = componentSet.children.filter(c => c.type === 'COMPONENT');
const properties = {};
for (const variant of variants) {
const props = variant.name.split(', ').reduce((acc, pair) => {
const [key, value] = pair.split('=');
acc[key.trim()] = value.trim();
return acc;
}, {});
for (const [key, value] of Object.entries(props)) {
if (!properties[key]) properties[key] = new Set();
properties[key].add(value);
}
}
// Convert Sets to arrays
const stateMachine = Object.fromEntries(
Object.entries(properties).map(([k, v]) => [k, Array.from(v)])
);
// Map to CSS pseudo-classes
const cssPseudoMap = {};
if (stateMachine.State) {
cssPseudoMap.State = {
'Hover': ':hover',
'Active': ':active',
'Focus': ':focus',
'Disabled': ':disabled'
};
}
return { stateMachine, cssPseudoMap, variantCount: variants.length };
```
#### figma-deep-component
Unlimited-depth component tree with resolved tokens and mainComponent refs.
**Example: Deep traverse component**
```javascript
async function deepTraverse(node, depth = 0) {
const result = {
id: node.id,
name: node.name,
type: node.type,
depth
};
if (node.type === 'INSTANCE') {
result.mainComponent = {
id: node.mainComponent.id,
name: node.mainComponent.name,
key: node.mainComponent.key
};
}
// Resolve bound variables
if (node.boundVariables) {
result.boundVariables = {};
for (const [field, binding] of Object.entries(node.boundVariables)) {
if (binding.type === 'VARIABLE_ALIAS') {
const variable = await figma.variables.getVariableByIdAsync(binding.id);
result.boundVariables[field] = {
name: variable.name,
value: variable.valuesByMode[Object.keys(variable.valuesByMode)[0]]
};
}
}
}
if ('children' in node) {
result.children = await Promise.all(
node.children.map(child => deepTraverse(child, depth + 1))
);
}
return result;
}
const selected = figma.currentPage.selection[0];
return await deepTraverse(selected);
```
### ♿ Quality & Accessibility
#### figma-lint-design
WCAG 2.2 + design-system quality lint over node tree.
**Example: Color contrast check**
```javascript
function getContrast(fg, bg) {
const luminance = (c) => {
const val = c / 255;
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
};
const l1 = 0.2126 * luminance(fg.r * 255) + 0.7152 * luminance(fg.g * 255) + 0.0722 * luminance(fg.b * 255);
const l2 = 0.2126 * luminance(bg.r * 255) + 0.7152 * luminance(bg.g * 255) + 0.0722 * luminance(bg.b * 255);
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}
const issues = [];
const textNodes = figma.currentPage.findAll(n => n.type === 'TEXT');
for (const node of textNodes) {
const fontSize = node.fontSize;
const fills = node.fills;
if (fills.length > 0 && fills[0].type === 'SOLID') {
const parent = node.parent;
let bgColor = { r: 1, g: 1, b: 1 }; // default white
if (parent.fills && parent.fills.length > 0 && parent.fills[0].type === 'SOLID') {
bgColor = parent.fills[0].color;
}
const contrast = getContrast(fills[0].color, bgColor);
const minContrast = fontSize >= 18 ? 3 : 4.5; // WCAG AA
if (contrast < minContrast) {
issues.push({
nodeId: node.id,
nodeName: node.name,
issue: `Insufficient contrast: ${contrast.toFixed(2)}:1 (needs ${minContrast}:1)`,
wcag: 'WCAG 2.2 Level AA - 1.4.3 Contrast (Minimum)'
});
}
}
}
return { totalIssues: issues.length, issues };
```
#### figma-audit-accessibility
Per-component a11y scorecard: state coverage, focus, target size.
**Example: Check touch target size**
```javascript
const MIN_TOUCH_TARGET = 44; // WCAG 2.2 AAA
const interactiveNodes = figma.currentPage.findAll(n =>
n.type === 'INSTANCE' &&
Auf GitHub ansehen