| name | moai-design-systems |
| description | Design system patterns, W3C DTCG 2025.10 token architecture, WCAG 2.2 accessibility standards, and Figma MCP workflows for consistent, accessible UI development |
| allowed-tools | Read, Write, WebFetch, WebSearch |
| tier | medium |
| version | 1.0.0 |
| updated | Tue Nov 04 2025 00:00:00 GMT+0000 (Coordinated Universal Time) |
| tags | design-systems, design-tokens, accessibility, wcag, figma-mcp, dtcg |
| status | stable |
Design Systems Development Skill
Purpose: Guide implementation of production-ready design systems using W3C DTCG 2025.10 token standards, WCAG 2.2 accessibility compliance, and Figma MCP automation workflows.
When to use this Skill:
- Setting up design token architecture for multi-platform projects
- Implementing accessible component libraries (WCAG 2.2 AA/AAA)
- Automating design-to-code workflows with Figma MCP
- Building maintainable design systems with Storybook
- Ensuring color contrast compliance and semantic token naming
Latest Standards (as of November 2025):
- DTCG Specification: 2025.10 (first stable version)
- WCAG Guidelines: 2.2 (AA: 4.5:1 text, AAA: 7:1 text)
- Figma MCP: Desktop + Remote server support
- Style Dictionary: 4.0 (DTCG-compatible)
- Storybook: 8.x (with Docs addon)
Progressive Disclosure Structure
Level 1: Quick Start Overview (Read This First)
Design System Foundation - Three Pillars:
Design Tokens (Single Source of Truth)
- Color, typography, spacing, borders, shadows
- Semantic naming:
color.primary.500,spacing.md,font.heading.lg - Multi-theme support (light/dark modes)
- Format: W3C DTCG 2025.10 JSON or Style Dictionary 4.0
Component Library (Atomic Design Pattern)
- Atoms → Molecules → Organisms → Templates → Pages
- Props API for reusability and composition
- Variant states: default, hover, active, disabled, error, loading
- Documentation: Storybook with auto-generated props/usage
Accessibility Standards (WCAG 2.2 Compliance)
- Color contrast: 4.5:1 (AA), 7:1 (AAA) for text
- Keyboard navigation: Tab order, focus management
- Screen readers: ARIA roles, labels, live regions
- Motion:
prefers-reduced-motionsupport
Tool Ecosystem Quick Reference:
| Tool | Version | Purpose | Official Link |
|---|---|---|---|
| W3C DTCG | 2025.10 | Design token specification | https://designtokens.org |
| Style Dictionary | 4.0+ | Token transformation engine | https://styledictionary.com |
| Figma MCP | Latest | Design-to-code automation | https://help.figma.com/hc/en-us/articles/32132100833559 |
| Storybook | 8.x | Component documentation | https://storybook.js.org |
| axe DevTools | Latest | Accessibility testing | https://www.deque.com/axe/devtools/ |
| Chromatic | Latest | Visual regression testing | https://chromatic.com |
Decision Points Checklist:
- Choose token format: DTCG 2025.10 or Style Dictionary 4.0 (both compatible)
- Target WCAG level: AA (4.5:1) or AAA (7:1) contrast
- Component pattern: Atomic Design or alternative structure
- Documentation tool: Storybook, zeroheight, or custom
- Figma integration: MCP server (desktop vs remote)
- Testing strategy: Visual regression + accessibility + interaction
Level 2: Implementation Patterns (How to Build)
Pattern 1: Design Token Architecture (DTCG 2025.10)
Token Structure - Semantic Naming Convention:
{
"$schema": "https://tr.designtokens.org/format/",
"$tokens": {
"color": {
"$type": "color",
"primary": {
"50": { "$value": "#eff6ff" },
"100": { "$value": "#dbeafe" },
"500": { "$value": "#3b82f6" },
"900": { "$value": "#1e3a8a" }
},
"semantic": {
"text": {
"primary": { "$value": "{color.gray.900}" },
"secondary": { "$value": "{color.gray.600}" },
"disabled": { "$value": "{color.gray.400}" }
},
"background": {
"default": { "$value": "{color.white}" },
"elevated": { "$value": "{color.gray.50}" }
}
}
},
"spacing": {
"$type": "dimension",
"xs": { "$value": "0.25rem" },
"sm": { "$value": "0.5rem" },
"md": { "$value": "1rem" },
"lg": { "$value": "1.5rem" },
"xl": { "$value": "2rem" }
},
"typography": {
"$type": "fontFamily",
"sans": { "$value": ["Inter", "system-ui", "sans-serif"] },
"mono": { "$value": ["JetBrains Mono", "monospace"] }
},
"fontSize": {
"$type": "dimension",
"sm": { "$value": "0.875rem" },
"base": { "$value": "1rem" },
"lg": { "$value": "1.125rem" },
"xl": { "$value": "1.25rem" }
}
}
}
Multi-Theme Support (Light/Dark Mode):
{
"color": {
"semantic": {
"background": {
"$type": "color",
"default": {
"$value": "{color.white}",
"$extensions": {
"mode": {
"dark": "{color.gray.900}"
}
}
}
}
}
}
}
Style Dictionary Configuration (v4.0+):
// style-dictionary.config.js
export default {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'build/css/',
files: [{
destination: 'variables.css',
format: 'css/variables'
}]
},
js: {
transformGroup: 'js',
buildPath: 'build/js/',
files: [{
destination: 'tokens.js',
format: 'javascript/es6'
}]
}
}
};
Pattern 2: Atomic Design Component Structure
Folder Hierarchy:
src/design-system/
├── tokens/ # Design tokens (DTCG format)
│ ├── color.json
│ ├── typography.json
│ └── spacing.json
├── components/
│ ├── atoms/ # Basic building blocks
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.stories.tsx
│ │ │ ├── Button.test.tsx
│ │ │ └── index.ts
│ │ ├── Input/
│ │ └── Icon/
│ ├── molecules/ # Simple combinations
│ │ ├── FormField/
│ │ ├── SearchBar/
│ │ └── Card/
│ ├── organisms/ # Complex sections
│ │ ├── Header/
│ │ ├── Footer/
│ │ └── DataTable/
│ └── templates/ # Page layouts
│ ├── DashboardLayout/
│ └── AuthLayout/
└── styles/
├── global.css
└── theme.css
Component Props API (Reusability Pattern):
// atoms/Button/Button.tsx
import { forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-primary-500 text-white hover:bg-primary-600',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
outline: 'border border-gray-300 bg-transparent hover:bg-gray-100',
ghost: 'hover:bg-gray-100',
danger: 'bg-red-500 text-white hover:bg-red-600'
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-base',
lg: 'h-12 px-6 text-lg'
}
},
defaultVariants: {
variant: 'primary',
size: 'md'
}
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
isLoading?: boolean;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, isLoading, children, disabled, ...props }, ref) => {
return (
<button
ref={ref}
className={buttonVariants({ variant, size, className })}
disabled={disabled || isLoading}
aria-busy={isLoading}
{...props}
>
{isLoading ? <Spinner /> : children}
</button>
);
}
);
Button.displayName = 'Button';
Pattern 3: WCAG 2.2 Accessibility Implementation
Color Contrast Validation (Automated Check):
// utils/a11y/contrast.ts
/**
* Calculate relative luminance for WCAG compliance
* @see https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
*/
function getLuminance(rgb: [number, number, number]): number {
const [r, g, b] = rgb.map(val => {
const sRGB = val / 255;
return sRGB <= 0.03928
? sRGB / 12.92
: Math.pow((sRGB + 0.055) / 1.055, 2.4);
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
/**
* Calculate contrast ratio between two colors
* WCAG AA: 4.5:1 (normal text), 3:1 (large text)
* WCAG AAA: 7:1 (normal text), 4.5:1 (large text)
*/
export function getContrastRatio(
foreground: string,
background: string
): number {
const fgLum = getLuminance(hexToRgb(foreground));
const bgLum = getLuminance(hexToRgb(background));
const lighter = Math.max(fgLum, bgLum);
const darker = Math.min(fgLum, bgLum);
return (lighter + 0.05) / (darker + 0.05);
}
/**
* Check if color pair meets WCAG AA/AAA requirements
*/
export function meetsWCAG(
foreground: string,
background: string,
level: 'AA' | 'AAA' = 'AA',
isLargeText: boolean = false
): boolean {
const ratio = getContrastRatio(foreground, background);
if (level === 'AAA') {
return isLargeText ? ratio >= 4.5 : ratio >= 7;
}
// AA level
return isLargeText ? ratio >= 3 : ratio >= 4.5;
}
Keyboard Navigation (Focus Management):
// hooks/useKeyboardNavigation.ts
import { useEffect, useRef } from 'react';
export function useKeyboardNavigation<T extends HTMLElement>(
options: {
onEscape?: () => void;
onEnter?: () => void;
trapFocus?: boolean;
} = {}
) {
const elementRef = useRef<T>(null);
useEffect(() => {
const element = elementRef.current;
if (!element) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
options.onEscape?.();
} else if (e.key === 'Enter') {
options.onEnter?.();
} else if (e.key === 'Tab' && options.trapFocus) {
// Focus trap implementation
const focusableElements = element.querySelectorAll<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
};
element.addEventListener('keydown', handleKeyDown);
return () => element.removeEventListener('keydown', handleKeyDown);
}, [options]);
return elementRef;
}
ARIA Labels & Roles (Screen Reader Support):
// components/atoms/Input/Input.tsx
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, required, ...props }, ref) => {
const inputId = useId();
const errorId = `${inputId}-error`;
return (
<div className="form-field">
<label htmlFor={inputId} className="form-label">
{label}
{required && <span aria-label="required">*</span>}
</label>
<input
ref={ref}
id={inputId}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
aria-required={required}
{...props}
/>
{error && (
<span id={errorId} role="alert" className="error-message">
{error}
</span>
)}
</div>
);
}
);
Motion Accessibility (Reduced Motion Support):
/* styles/motion.css */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Safe animations for reduced motion users */
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 200ms ease-in;
}
@media (prefers-reduced-motion: reduce) {
.fade-enter-active {
transition: none;
opacity: 1;
}
}
Pattern 4: Figma MCP Integration Workflow
Setup (Desktop Server):
# Install Figma desktop app
# Enable MCP server in settings
# Desktop server runs at http://127.0.0.1:3845/mcp
MCP Configuration (Claude Desktop):
{
"mcpServers": {
"figma": {
"command": "figma-mcp",
"args": [],
"env": {
"FIGMA_ACCESS_TOKEN": "your-figma-token"
}
}
}
}
Extracting Design Tokens from Figma:
- Create Figma Variables (Color, Typography, Spacing)
- Use MCP Server to extract variables:
- Select frame in Figma
- Prompt: "Extract all design tokens from this frame"
- MCP returns DTCG-compatible JSON
- Transform to Code using Style Dictionary
Component Code Generation:
User Workflow:
1. Select component frame in Figma
2. Prompt: "Generate React component from this design"
3. MCP extracts:
- Component structure
- Applied design tokens
- Layout properties (flex, grid)
- Typography and spacing
4. Output: TypeScript React component with props
Automation Pattern (Link-based workflow):
// scripts/sync-figma-tokens.ts
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
async function syncFigmaTokens(figmaFileUrl: string) {
// Use Figma MCP to extract tokens
const { stdout } = await execAsync(
`figma-mcp extract-tokens --url="${figmaFileUrl}"`
);
const tokens = JSON.parse(stdout);
// Write to tokens directory
await writeFile('tokens/color.json', JSON.stringify(tokens.color, null, 2));
await writeFile('tokens/spacing.json', JSON.stringify(tokens.spacing, null, 2));
// Run Style Dictionary build
await execAsync('npm run tokens:build');
console.log('✅ Design tokens synchronized from Figma');
}
Pattern 5: Storybook Documentation Setup
Installation & Configuration:
npx storybook@latest init
Storybook Configuration (v8.x):
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y', // Accessibility testing
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag', // Auto-generate docs
},
};
export default config;
Component Story (with accessibility tests):
// components/atoms/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Atoms/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'outline', 'ghost', 'danger'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
children: 'Primary Button',
variant: 'primary',
},
};
export const AllVariants: Story = {
render: () => (
<div className="flex gap-4">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>
</div>
),
};
export const Disabled: Story = {
args: {
children: 'Disabled Button',
disabled: true,
},
};
export const Loading: Story = {
args: {
children: 'Loading Button',
isLoading: true,
},
};
Level 3: Advanced Topics (Deep Expertise)
Advanced 1: Type-Safe Design Tokens (TypeScript)
Generate TypeScript Types from DTCG Tokens:
// scripts/generate-token-types.ts
import { readFileSync, writeFileSync } from 'fs';
interface DTCGToken {
$value: string | number | string[];
$type?: string;
[key: string]: any;
}
function generateTypes(tokens: Record<string, any>, prefix = ''): string {
let types = '';
for (const [key, value] of Object.entries(tokens)) {
if (value.$value !== undefined) {
const tokenPath = `${prefix}${key}`.replace(/\./g, '-');
types += `export const ${tokenPath} = '${value.$value}';\n`;
} else {
types += generateTypes(value, `${prefix}${key}.`);
}
}
return types;
}
const colorTokens = JSON.parse(readFileSync('tokens/color.json', 'utf-8'));
const types = generateTypes(colorTokens.$tokens);
writeFileSync('src/tokens/colors.ts', types);
Advanced 2: Visual Regression Testing (Chromatic)
# Install Chromatic
npm install --save-dev chromatic
# Configure in package.json
{
"scripts": {
"chromatic": "chromatic --project-token=<your-token>"
}
}
CI/CD Integration:
# .github/workflows/chromatic.yml
name: Chromatic
on: [push]
jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Run Chromatic
uses: chromaui/action@v1
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
Advanced 3: Accessibility Testing Automation
Jest + jest-axe Configuration:
// tests/setup.ts
import '@testing-library/jest-dom';
import { toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
Component Accessibility Tests:
// components/atoms/Button/Button.test.tsx
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';
import { Button } from './Button';
describe('Button Accessibility', () => {
it('should have no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it('should have correct ARIA attributes when disabled', () => {
const { getByRole } = render(<Button disabled>Disabled</Button>);
const button = getByRole('button');
expect(button).toHaveAttribute('aria-disabled', 'true');
});
it('should indicate loading state to screen readers', () => {
const { getByRole } = render(<Button isLoading>Loading</Button>);
const button = getByRole('button');
expect(button).toHaveAttribute('aria-busy', 'true');
});
});
Best Practices Checklist
Design Token Architecture:
- Use semantic naming (
color.primary.500notcolor.blue) - Implement aliasing for themes (
{color.white}references) - Validate DTCG 2025.10 spec compliance
- Version tokens with semantic versioning
- Document token usage in Storybook
Component Development:
- Follow Atomic Design hierarchy (Atoms → Molecules → Organisms)
- Create variant-based props APIs (not separate components)
- Document all props with TypeScript types
- Write Storybook stories for all variants
- Test component accessibility with jest-axe
Accessibility:
- Verify 4.5:1 contrast for all text (WCAG AA)
- Implement keyboard navigation for all interactive elements
- Add ARIA labels to form fields and buttons
- Test with screen readers (NVDA, JAWS, VoiceOver)
- Support
prefers-reduced-motion
Testing:
- Visual regression tests for all components (Chromatic)
- Accessibility tests with axe-core
- Interaction tests with Testing Library
- Cross-browser compatibility checks
Figma Integration:
- Set up Figma MCP server (desktop or remote)
- Extract design tokens from Figma variables
- Automate component code generation
- Sync design changes with codebase
When NOT to Use This Skill
- Simple static sites: Overkill for projects without complex UI requirements
- Rapid prototyping: Design systems add overhead during early exploration
- Single-use projects: Token architecture benefits long-term maintenance
- Non-web platforms: This Skill focuses on web (React/Vue/TypeScript)
For these cases, consider:
- Plain CSS/Tailwind for static sites
- Component libraries (Material-UI, shadcn/ui) for rapid development
- Platform-specific design systems (iOS HIG, Material Design for Android)
Related Skills:
moai-domain-frontend- Frontend architecture patternsmoai-lang-typescript- TypeScript best practicesmoai-foundation-testing- Testing strategies
Official Resources:
- W3C DTCG: https://designtokens.org
- WCAG 2.2: https://www.w3.org/WAI/WCAG22/quickref/
- Figma MCP: https://help.figma.com/hc/en-us/articles/32132100833559
- Style Dictionary: https://styledictionary.com
- Storybook: https://storybook.js.org