| name | figmalint-design-system-auditing |
| description | AI-powered Figma plugin for auditing components for design system compliance, accessibility, and developer readiness |
| triggers | ["audit my Figma component for design system compliance","analyze Figma components for accessibility issues","detect design tokens and hard-coded values in Figma","generate component documentation from Figma","fix design token usage in Figma components","export Figma component specs for developers","check Figma component readiness score","integrate FigmaLint into my workflow"] |
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
Development Installation
git clone https://github.com/southleft/figmalint.git
cd figmalint
npm install
npm run build
Development Commands
npm run dev
npm run build
npm run lint
npm run clean
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:
process.env.ANTHROPIC_API_KEY
process.env.OPENAI_API_KEY
process.env.GOOGLE_API_KEY
Provider Configuration
export interface AIProvider {
id: string;
name: string;
models: AIModel[];
call: (request: AIRequest) => Promise<AIResponse>;
parseKey?: (key: string) => boolean;
}
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
import { analyzeComponent } from './core/component-analyzer';
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
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<{ : ; : ; : }>;
};
: ;
}
(): <> {
(node, {
: ,
: ,
: ,
:
});
}
Auto-Fix Token Binding
import { bindHardCodedValueToToken } from './fixes/token-fixer';
interface TokenBindingOptions {
searchLocal: boolean;
searchLibraries: boolean;
fuzzyMatch: boolean;
propertyAwareScoring: boolean;
}
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}`);
}
}
async function fixSpacingToken() {
({
nodeId,
: ,
: hardCodedSpacing,
: ,
: {
: ,
: ,
: ,
:
}
});
}
Layer Naming Auto-Fix
import { suggestLayerName, applyLayerRename } from './fixes/naming-fixer';
type NamingStrategy = 'semantic' | 'bem' | 'prefix' | 'kebab' | 'camel' | 'snake';
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}"`);
await applyLayerRename(node.id, suggestion.suggestedName);
}
}
Accessibility Auditing
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)`);
}
(!accessibilityChecks..) {
.();
}
}
Component State Detection
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);
console.log('Missing states:', statesCoverage.missing);
}
AI-Powered Description Generation
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.
};
}
Export Formats
type ExportFormat = 'markdown' | 'ai-prompt' | 'json';
async function exportComponent(node: ComponentNode, format: ExportFormat) {
const analysis = await analyzeComponent(node);
switch (format) {
case 'markdown':
return generateMarkdownExport(analysis);
case 'ai-prompt':
return generateAIPromptExport(analysis);
case 'json':
return JSON.stringify(analysis, null, 2);
}
}
Design Systems Chat
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
});
context.conversationHistory.push(
{ role: 'user', content: question, timestamp: Date.now() },
{ : , : response, : .() }
);
response;
}
Common Patterns
Full Component Audit Workflow
async function auditComponent(componentNode: ComponentNode) {
const analysis = await analyzeComponent(componentNode, {
includeTokens: true,
includeAccessibility: true,
includeStates: true,
includeNaming: true
});
console.log(`Readiness Score: ${analysis.readinessScore}/100`);
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');
}
(analysis.... > ) {
( hardCoded analysis...) {
({
: hardCoded.,
: hardCoded.,
: hardCoded.,
:
});
}
}
markdown = (componentNode, );
aiPrompt = (componentNode, );
{ analysis, issues, markdown, aiPrompt };
}
Batch Token Fixing
async function fixAllTokens(componentNode: ComponentNode) {
const analysis = await analyzeComponent(componentNode);
const { hardCodedValues } = analysis.tokenAnalysis;
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
}
});
}
for (const spacing of hardCodedValues.spacing) {
await bindHardCodedValueToToken({
nodeId: spacing.nodeId,
property: spacing.property,
hardCodedValue: spacing.value,
tokenType: ,
: {
: ,
: ,
: ,
:
}
});
}
.();
}
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 = [];
if (analysis.accessibilityChecks.contrastRatio.ratio < rules.minContrastRatio) {
results.push({
type: 'contrast',
severity: 'error',
message: `Contrast ratio ${analysis.accessibilityChecks.contrastRatio.ratio} is below ${rules.minContrastRatio}`
});
}
const { width, height } = analysis.accessibilityChecks.touchTargets.actualSize;
if (width < rules.minTouchTargetSize || height < rules.minTouchTargetSize) {
results.push({
type: 'touch-target',
: ,
:
});
}
(analysis... < rules.) {
results.({
: ,
: ,
:
});
}
(rules. && !analysis...) {
results.({
: ,
: ,
:
});
}
results;
}
Troubleshooting
API Provider Issues
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;
}
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
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}`);
const matches = findMatchingTokens(hardCodedValue, tokenType, {
variables: [...localVars, ...libraryVars],
fuzzyMatch: true,
propertyAwareScoring: true
});
console.log('Matches found:', matches);
return matches;
}
Performance Optimization
async function batchAnalyze(componentNodes: ComponentNode[]) {
const results = await Promise.all(
componentNodes.map(node =>
analyzeComponent(node, {
includeTokens: true,
includeAccessibility: false,
includeStates: true,
includeNaming: false
})
)
);
return 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);
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
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
async function syncToDocumentation(componentNode: ComponentNode, platform: string) {
const markdown = await exportComponent(componentNode, 'markdown');
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()
})
});
}