- 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](https://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
```bash
# Install directly from Figma Community
# Visit: https://www.figma.com/community/plugin/1521241390290871981/figmalint
# Click "Install" button
```
### Development Installation
```bash
# 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
```bash
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:
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
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
```typescript
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,
Ver no GitHub