figmalint-design-system-auditing
AI-powered Figma plugin for auditing components for design system compliance, accessibility, and developer readiness
How do I install this agent skill?
npx skills add https://github.com/reason-machines/design-skills --skill figmalint-design-system-auditingIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill facilitates design system auditing for Figma components. It includes instructions for external installation and code snippets for AI-powered analysis. A minor risk is present due to the processing of untrusted component data which could lead to indirect prompt injection.
- Socketpass
No alerts
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
FigmaLint Design System Auditing Skill
Skill by ara.so — Design Skills collection
Overview
FigmaLint is an AI-powered Figma plugin that audits components for design system compliance, accessibility standards (WCAG), and developer handoff readiness. It analyzes components, detects design tokens vs hard-coded values, identifies missing interactive states, and generates structured documentation for developer handoff or AI code generation.
Key capabilities:
- Multi-provider AI analysis (Anthropic Claude, OpenAI GPT, Google Gemini)
- Design token detection and auto-fix binding
- Accessibility auditing (contrast, touch targets, focus indicators)
- Component state coverage analysis
- Auto-fix for tokens and layer naming
- Export to Markdown, AI Prompt, or JSON
Installation
From Figma Community
# Install directly from Figma Community
# Visit: https://www.figma.com/community/plugin/1521241390290871981/figmalint
# Click "Install" button
Development Installation
# Clone the repository
git clone https://github.com/southleft/figmalint.git
cd figmalint
# Install dependencies
npm install
# Build the plugin
npm run build
# In Figma Desktop App:
# Plugins > Development > Import plugin from manifest
# Select manifest.json from the project root
Development Commands
npm run dev # Development build with watch mode
npm run build # Production build
npm run lint # TypeScript type checking
npm run clean # Clean build artifacts
Architecture Overview
FigmaLint follows a modular architecture:
src/
├── code.ts # Plugin entry point
├── types.ts # TypeScript definitions
├── api/
│ ├── claude.ts # Prompt construction
│ └── providers/ # AI provider implementations
│ ├── anthropic.ts
│ ├── openai.ts
│ └── google.ts
├── core/
│ ├── component-analyzer.ts # Component analysis
│ ├── token-analyzer.ts # Token detection
│ └── consistency-engine.ts # Design system checks
├── fixes/
│ ├── token-fixer.ts # Auto-fix token binding
│ └── naming-fixer.ts # Layer renaming
└── utils/
└── figma-helpers.ts # Figma API utilities
Configuration
API Provider Setup
FigmaLint supports three AI providers. Set up API keys using environment variables:
// API keys are stored in Figma's local storage per provider
// Never hardcode keys in code
// For Anthropic Claude
process.env.ANTHROPIC_API_KEY
// For OpenAI GPT
process.env.OPENAI_API_KEY
// For Google Gemini
process.env.GOOGLE_API_KEY
Provider Configuration
// src/api/providers/types.ts
export interface AIProvider {
id: string;
name: string;
models: AIModel[];
call: (request: AIRequest) => Promise<AIResponse>;
parseKey?: (key: string) => boolean;
}
// Available providers
const PROVIDERS = {
anthropic: {
models: ['claude-opus-4.5', 'claude-sonnet-4.5', 'claude-haiku-4.5']
},
openai: {
models: ['gpt-5.2', 'gpt-5.2-pro', 'gpt-5-mini']
},
google: {
models: ['gemini-3-pro', 'gemini-2.5-pro', 'gemini-2.5-flash']
}
};
Core Functionality
Component Analysis
// src/core/component-analyzer.ts
import { analyzeComponent } from './core/component-analyzer';
// Analyze a Figma component
async function analyzeComponentNode(node: ComponentNode) {
const analysis = await analyzeComponent(node, {
includeTokens: true,
includeAccessibility: true,
includeStates: true,
includeNaming: true
});
return {
metadata: analysis.metadata,
tokens: analysis.tokenAnalysis,
states: analysis.statesCoverage,
accessibility: analysis.accessibilityChecks,
readiness: analysis.readinessScore
};
}
Token Detection
// src/core/token-analyzer.ts
import { analyzeTokens } from './core/token-analyzer';
interface TokenAnalysis {
tokensByType: {
colors: Array<{ name: string; value: string; boundNodes: string[] }>;
spacing: Array<{ name: string; value: number; boundNodes: string[] }>;
typography: Array<{ name: string; fontFamily: string; fontSize: number }>;
effects: Array<{ name: string; type: string }>;
borders: Array<{ name: string; strokeWeight: number }>;
};
hardCodedValues: {
colors: Array<{ nodeId: string; value: string; property: string }>;
spacing: Array<{ nodeId: string; value: number; property: string }>;
};
tokenAdoptionRate: number;
}
async function detectTokens(node: ComponentNode): Promise<TokenAnalysis> {
return analyzeTokens(node, {
includeLocalVariables: true,
includeLibraryVariables: true,
includeStyles: true,
deduplicatePerNode: true
});
}
Auto-Fix Token Binding
// src/fixes/token-fixer.ts
import { bindHardCodedValueToToken } from './fixes/token-fixer';
interface TokenBindingOptions {
searchLocal: boolean;
searchLibraries: boolean;
fuzzyMatch: boolean;
propertyAwareScoring: boolean;
}
// Bind a hard-coded color to a design token
async function fixColorToken(nodeId: string, hardCodedColor: string) {
const result = await bindHardCodedValueToToken({
nodeId,
property: 'fills',
hardCodedValue: hardCodedColor,
tokenType: 'color',
options: {
searchLocal: true,
searchLibraries: true,
fuzzyMatch: true,
propertyAwareScoring: true
}
});
if (result.success) {
console.log(`Bound to token: ${result.tokenName}`);
}
}
// Bind spacing values
async function fixSpacingToken(nodeId: string, hardCodedSpacing: number) {
await bindHardCodedValueToToken({
nodeId,
property: 'paddingLeft',
hardCodedValue: hardCodedSpacing,
tokenType: 'spacing',
options: {
searchLocal: true,
searchLibraries: true,
fuzzyMatch: true,
propertyAwareScoring: true
}
});
}
Layer Naming Auto-Fix
// src/fixes/naming-fixer.ts
import { suggestLayerName, applyLayerRename } from './fixes/naming-fixer';
type NamingStrategy = 'semantic' | 'bem' | 'prefix' | 'kebab' | 'camel' | 'snake';
// Detect generic names and suggest semantic alternatives
async function fixLayerNaming(node: SceneNode, strategy: NamingStrategy = 'semantic') {
const suggestion = suggestLayerName(node, strategy);
if (suggestion.isGeneric) {
console.log(`Generic name detected: "${suggestion.currentName}"`);
console.log(`Suggested: "${suggestion.suggestedName}"`);
// Apply the rename
await applyLayerRename(node.id, suggestion.suggestedName);
}
}
// Recognizes 30+ semantic layer types:
// icon, button, label, badge, avatar, card, header, footer,
// navigation, sidebar, modal, dropdown, input, checkbox, etc.
Accessibility Auditing
// Accessibility checks included in component analysis
interface AccessibilityChecks {
contrastRatio: {
pass: boolean;
ratio: number;
wcagLevel: 'AA' | 'AAA' | 'fail';
};
touchTargets: {
pass: boolean;
minSize: number;
actualSize: { width: number; height: number };
};
focusIndicators: {
pass: boolean;
hasVisibleFocus: boolean;
};
fontSize: {
pass: boolean;
minSize: number;
actualSize: number;
};
}
async function checkAccessibility(node: ComponentNode) {
const analysis = await analyzeComponent(node);
const { accessibilityChecks } = analysis;
if (!accessibilityChecks.contrastRatio.pass) {
console.warn(`Contrast ratio: ${accessibilityChecks.contrastRatio.ratio} (fail)`);
}
if (!accessibilityChecks.touchTargets.pass) {
console.warn(`Touch target too small: ${accessibilityChecks.touchTargets.actualSize.width}x${accessibilityChecks.touchTargets.actualSize.height}`);
}
}
Component State Detection
// Detect missing interactive states
interface StatesCoverage {
detected: string[];
missing: string[];
variants: Array<{
name: string;
properties: Record<string, string>;
}>;
}
async function checkComponentStates(node: ComponentSetNode) {
const analysis = await analyzeComponent(node);
const { statesCoverage } = analysis;
console.log('Detected states:', statesCoverage.detected);
// Example: ['default', 'hover', 'pressed']
console.log('Missing states:', statesCoverage.missing);
// Example: ['focus', 'disabled', 'active']
// Interactive states checked:
// hover, focus, disabled, pressed, active, selected, error, loading
}
AI-Powered Description Generation
// Generate structured component description
interface ComponentDescription {
summary: string;
sections: {
purpose: string;
behavior: string;
composition: string;
usage: string;
codeGenerationNotes: string;
};
nestedComponents: string[];
currentDescription: string;
matches: boolean;
}
async function generateDescription(node: ComponentNode, provider: string, model: string, apiKey: string) {
const prompt = buildDescriptionPrompt(node);
const response = await callAIProvider({
provider,
model,
apiKey,
prompt,
systemPrompt: 'You are a design systems expert generating component documentation.'
});
return {
summary: response.summary,
sections: response.sections,
nestedComponents: response.nestedComponents,
matches: node.description === response.generatedDescription
};
}
Export Formats
// Export component documentation
type ExportFormat = 'markdown' | 'ai-prompt' | 'json';
async function exportComponent(node: ComponentNode, format: ExportFormat) {
const analysis = await analyzeComponent(node);
switch (format) {
case 'markdown':
// Comprehensive documentation for design system sites
return generateMarkdownExport(analysis);
case 'ai-prompt':
// Structured spec for AI code generation
return generateAIPromptExport(analysis);
case 'json':
// Complete analysis data for programmatic use
return JSON.stringify(analysis, null, 2);
}
}
// Markdown export includes:
// - Component metadata and variants table
// - Properties API reference
// - Interactive states (pass/fail status)
// - Design token breakdown (tokens vs hard-coded)
// - Accessibility audit results
// - Component readiness score
// - AI interpretation
Design Systems Chat
// Multi-turn conversational interface
interface ChatMessage {
role: 'user' | 'assistant';
content: string;
timestamp: number;
}
interface ChatContext {
componentId: string;
analysis: ComponentAnalysis;
conversationHistory: ChatMessage[];
}
async function askAboutComponent(question: string, context: ChatContext) {
const prompt = buildChatPrompt(question, context);
const response = await callAIProvider({
provider: context.provider,
model: context.model,
apiKey: process.env[`${context.provider.toUpperCase()}_API_KEY`],
prompt,
conversationHistory: context.conversationHistory
});
// Update conversation history
context.conversationHistory.push(
{ role: 'user', content: question, timestamp: Date.now() },
{ role: 'assistant', content: response, timestamp: Date.now() }
);
return response;
}
Common Patterns
Full Component Audit Workflow
async function auditComponent(componentNode: ComponentNode) {
// 1. Analyze component
const analysis = await analyzeComponent(componentNode, {
includeTokens: true,
includeAccessibility: true,
includeStates: true,
includeNaming: true
});
// 2. Check readiness score
console.log(`Readiness Score: ${analysis.readinessScore}/100`);
// 3. Identify issues
const issues = [];
if (analysis.tokenAnalysis.hardCodedValues.colors.length > 0) {
issues.push(`${analysis.tokenAnalysis.hardCodedValues.colors.length} hard-coded colors`);
}
if (analysis.statesCoverage.missing.length > 0) {
issues.push(`Missing states: ${analysis.statesCoverage.missing.join(', ')}`);
}
if (!analysis.accessibilityChecks.contrastRatio.pass) {
issues.push('Contrast ratio fails WCAG standards');
}
// 4. Auto-fix issues
if (analysis.tokenAnalysis.hardCodedValues.colors.length > 0) {
for (const hardCoded of analysis.tokenAnalysis.hardCodedValues.colors) {
await bindHardCodedValueToToken({
nodeId: hardCoded.nodeId,
property: hardCoded.property,
hardCodedValue: hardCoded.value,
tokenType: 'color'
});
}
}
// 5. Export documentation
const markdown = await exportComponent(componentNode, 'markdown');
const aiPrompt = await exportComponent(componentNode, 'ai-prompt');
return { analysis, issues, markdown, aiPrompt };
}
Batch Token Fixing
async function fixAllTokens(componentNode: ComponentNode) {
const analysis = await analyzeComponent(componentNode);
const { hardCodedValues } = analysis.tokenAnalysis;
// Fix all hard-coded colors
for (const color of hardCodedValues.colors) {
await bindHardCodedValueToToken({
nodeId: color.nodeId,
property: color.property,
hardCodedValue: color.value,
tokenType: 'color',
options: {
searchLocal: true,
searchLibraries: true,
fuzzyMatch: true,
propertyAwareScoring: true
}
});
}
// Fix all hard-coded spacing
for (const spacing of hardCodedValues.spacing) {
await bindHardCodedValueToToken({
nodeId: spacing.nodeId,
property: spacing.property,
hardCodedValue: spacing.value,
tokenType: 'spacing',
options: {
searchLocal: true,
searchLibraries: true,
fuzzyMatch: true,
propertyAwareScoring: true
}
});
}
console.log('All tokens fixed');
}
Custom Accessibility Audit
interface CustomAccessibilityRules {
minContrastRatio: number;
minTouchTargetSize: number;
minFontSize: number;
requireFocusIndicator: boolean;
}
async function customAccessibilityAudit(
node: ComponentNode,
rules: CustomAccessibilityRules
) {
const analysis = await analyzeComponent(node);
const results = [];
// Contrast ratio
if (analysis.accessibilityChecks.contrastRatio.ratio < rules.minContrastRatio) {
results.push({
type: 'contrast',
severity: 'error',
message: `Contrast ratio ${analysis.accessibilityChecks.contrastRatio.ratio} is below ${rules.minContrastRatio}`
});
}
// Touch target size
const { width, height } = analysis.accessibilityChecks.touchTargets.actualSize;
if (width < rules.minTouchTargetSize || height < rules.minTouchTargetSize) {
results.push({
type: 'touch-target',
severity: 'error',
message: `Touch target ${width}x${height} is below minimum ${rules.minTouchTargetSize}px`
});
}
// Font size
if (analysis.accessibilityChecks.fontSize.actualSize < rules.minFontSize) {
results.push({
type: 'font-size',
severity: 'warning',
message: `Font size ${analysis.accessibilityChecks.fontSize.actualSize}px is below ${rules.minFontSize}px`
});
}
// Focus indicator
if (rules.requireFocusIndicator && !analysis.accessibilityChecks.focusIndicators.hasVisibleFocus) {
results.push({
type: 'focus',
severity: 'error',
message: 'Component missing visible focus indicator'
});
}
return results;
}
Troubleshooting
API Provider Issues
// Verify API key format
function validateApiKey(provider: string, key: string): boolean {
const patterns = {
anthropic: /^sk-ant-/,
openai: /^sk-/,
google: /^[A-Za-z0-9_-]+$/
};
return patterns[provider]?.test(key) ?? false;
}
// Test provider connection
async function testProviderConnection(provider: string, apiKey: string) {
try {
const response = await callAIProvider({
provider,
model: 'default',
apiKey,
prompt: 'Test',
systemPrompt: 'Reply with "OK"'
});
return { success: true, response };
} catch (error) {
return { success: false, error: error.message };
}
}
Token Binding Issues
// Debug token search
async function debugTokenSearch(hardCodedValue: string, tokenType: string) {
const localVars = await figma.variables.getLocalVariablesAsync();
const libraryVars = await figma.variables.getLibraryVariablesAsync();
console.log(`Searching for ${tokenType} matching:`, hardCodedValue);
console.log(`Local variables: ${localVars.length}`);
console.log(`Library variables: ${libraryVars.length}`);
// Search with fuzzy matching
const matches = findMatchingTokens(hardCodedValue, tokenType, {
variables: [...localVars, ...libraryVars],
fuzzyMatch: true,
propertyAwareScoring: true
});
console.log('Matches found:', matches);
return matches;
}
Performance Optimization
// Batch analyze multiple components
async function batchAnalyze(componentNodes: ComponentNode[]) {
const results = await Promise.all(
componentNodes.map(node =>
analyzeComponent(node, {
includeTokens: true,
includeAccessibility: false, // Skip for performance
includeStates: true,
includeNaming: false
})
)
);
return results;
}
// Cache analysis results
const analysisCache = new Map<string, ComponentAnalysis>();
async function getCachedAnalysis(node: ComponentNode) {
const cacheKey = `${node.id}-${node.lastModified}`;
if (analysisCache.has(cacheKey)) {
return analysisCache.get(cacheKey);
}
const analysis = await analyzeComponent(node);
analysisCache.set(cacheKey, analysis);
return analysis;
}
Error Handling
async function safeAnalyze(node: ComponentNode) {
try {
return await analyzeComponent(node);
} catch (error) {
if (error.message.includes('API key')) {
console.error('API key invalid or missing');
return { error: 'Invalid API key' };
}
if (error.message.includes('rate limit')) {
console.error('Rate limit exceeded, retrying in 60s');
await new Promise(resolve => setTimeout(resolve, 60000));
return safeAnalyze(node);
}
if (error.message.includes('node not found')) {
console.error('Component node deleted or inaccessible');
return { error: 'Node not found' };
}
throw error;
}
}
Integration Examples
CI/CD Pipeline Integration
// Export component specs for automated testing
async function exportForCI(componentSetId: string) {
const node = await figma.getNodeByIdAsync(componentSetId) as ComponentSetNode;
const analysis = await analyzeComponent(node);
return {
componentId: node.id,
componentName: node.name,
readinessScore: analysis.readinessScore,
tokenAdoption: analysis.tokenAnalysis.tokenAdoptionRate,
accessibilityPasses: Object.values(analysis.accessibilityChecks).every(c => c.pass),
missingStates: analysis.statesCoverage.missing,
exportedAt: new Date().toISOString()
};
}
Design System Documentation Sync
// Sync to external documentation platform
async function syncToDocumentation(componentNode: ComponentNode, platform: string) {
const markdown = await exportComponent(componentNode, 'markdown');
// Send to documentation platform API
await fetch(`https://api.${platform}.com/components`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DOCS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: componentNode.name,
content: markdown,
updatedAt: new Date().toISOString()
})
});
}
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/reason-machines/design-skills/figmalint-design-system-auditing">View figmalint-design-system-auditing on skillZs</a>