skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
community-access/accessibility-agents259 installs

design-system

Validate design system tokens for WCAG AA/AAA contrast. Compute color token contrast, focus ring validation (WCAG 2.4.13), motion tokens, and spacing for touch targets across frameworks.

How do I install this agent skill?

npx skills add https://github.com/community-access/accessibility-agents --skill design-system
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    This skill is safe. It provides reference data and scripts for validating design system tokens against WCAG accessibility standards. It includes standard shell commands for locating local configuration files and instructions for installing well-known development tools.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

  • ZeroLeakspass

    1 finding · Score: 86/100

What does this agent skill do?

Design System Accessibility Skill

This skill provides reference data for design token contrast validation, focus ring compliance, and spacing audits. Used by design-system-auditor.agent.md.


WCAG Contrast Ratio - Computation Reference

Step 1: Linearize sRGB Channel

For each channel C in [0, 255]:

c = C / 255
c_lin = c / 12.92              if c <= 0.04045
c_lin = ((c + 0.055) / 1.055)^2.4   otherwise

Step 2: Relative Luminance

L = 0.2126 * R_lin + 0.7152 * G_lin + 0.0722 * B_lin

Step 3: Contrast Ratio

ratio = (L_lighter + 0.05) / (L_darker + 0.05)

Quick JavaScript Implementation

function relativeLuminance(hex) {
  const c = hex.replace('#', '').match(/.{2}/g)
    .map(h => parseInt(h, 16) / 255)
    .map(c => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
  return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
}

function contrastRatio(hex1, hex2) {
  const L1 = relativeLuminance(hex1);
  const L2 = relativeLuminance(hex2);
  const lighter = Math.max(L1, L2);
  const darker = Math.min(L1, L2);
  return (lighter + 0.05) / (darker + 0.05);
}

// Example
contrastRatio('#6B7280', '#FFFFFF'); // 5.74:1 - PASSES AA (was a common misconception)
contrastRatio('#9CA3AF', '#FFFFFF'); // 2.85:1 - FAILS AA

HSL to Hex Conversion (for CSS variable tokens)

Many design systems store colors as HSL triplets (e.g., shadcn/ui, Radix):

function hslToHex(h, s, l) {
  s /= 100; l /= 100;
  const a = s * Math.min(l, 1 - l);
  const f = n => {
    const k = (n + h / 30) % 12;
    return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
  };
  return '#' + [f(0), f(8), f(4)]
    .map(x => Math.round(x * 255).toString(16).padStart(2, '0'))
    .join('');
}

// shadcn/ui: --muted-foreground: 215.4 16.3% 46.9%
hslToHex(215.4, 16.3, 46.9); // -> approximately #6B7280

WCAG Contrast Thresholds

Use CaseAAAAANotes
Normal text (< 18pt / < 14pt bold)4.5:17:1Most body text
Large text (>= 18pt / >= 14pt bold)3:14.5:1Headings, display text
UI components (borders, icons)3:1-Input borders, icon buttons
Focus indicators (WCAG 2.4.13, 2.2)3:1-Against adjacent colors
Placeholder text4.5:1-Counts as normal text
Disabled stateExemptExemptDocumented exemption
Logo / brandExemptExemptNo requirement
Decorative contentExemptExemptMust be marked decorative

Framework Token Paths - Complete Reference

Tailwind CSS

// tailwind.config.js / tailwind.config.ts
module.exports = {
  theme: {
    // Base colors (Tailwind default palette)
    colors: {
      // All color scales: slate, gray, zinc, neutral, stone, red, orange, amber,
      // yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet,
      // purple, fuchsia, pink, rose
      // Each scale: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950
    },
    extend: {
      colors: {
        // Custom semantic colors - CHECK ALL PAIRS
        brand: { primary: '#...', secondary: '#...' },
        background: '#...',
        foreground: '#...',
        muted: '#...',
        'muted-foreground': '#...',
        accent: '#...',
        'accent-foreground': '#...',
        destructive: '#...',
        'destructive-foreground': '#...',
        card: '#...',
        'card-foreground': '#...',
        popover: '#...',
        'popover-foreground': '#...',
        border: '#...',    // UI component - check 3:1 against background
        input: '#...',     // UI component - check 3:1 against background
        ring: '#...',      // Focus ring - check 3:1 against background
        primary: '#...',
        'primary-foreground': '#...',
        secondary: '#...',
        'secondary-foreground': '#...',
      },
      ringColor: { DEFAULT: '...' },  // Focus state
      ringWidth: { DEFAULT: '2px' },  // Must be >= 2px for WCAG 2.4.13
    }
  }
}

shadcn/ui / Radix CSS Variables

/* globals.css - HSL triplets without hsl() wrapper */
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --card: 0 0% 100%;
  --card-foreground: 222.2 84% 4.9%;
  --popover: 0 0% 100%;
  --popover-foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
  --secondary: 210 40% 96.1%;
  --secondary-foreground: 222.2 47.4% 11.2%;
  --muted: 210 40% 96.1%;
  --muted-foreground: 215.4 16.3% 46.9%;     /* HIGH RISK - check on --background */
  --accent: 210 40% 96.1%;
  --accent-foreground: 222.2 47.4% 11.2%;
  --destructive: 0 84.2% 60.2%;               /* HIGH RISK - red on white */
  --destructive-foreground: 210 40% 98%;
  --border: 214.3 31.8% 91.4%;               /* UI component - check 3:1 */
  --input: 214.3 31.8% 91.4%;                /* UI component - check 3:1 */
  --ring: 222.2 84% 4.9%;                    /* Focus ring - check 3:1 */
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  /* ... all dark mode variants */
}

Material UI (MUI) v5+

// Token paths in createTheme()
palette: {
  primary: {
    main: '#1976d2',            // text-on-white: 4.56:1 
    light: '#42a5f5',           // text-on-white: 2.86:1  (do not use as text color)
    dark: '#1565c0',            // text-on-white: 5.91:1 
    contrastText: '#fff',       // check on main
  },
  secondary: {
    main: '#9c27b0',            // text-on-white: 4.56:1  (barely)
    light: '#ba68c8',           // text-on-white: 2.55:1 
    dark: '#7b1fa2',
    contrastText: '#fff',
  },
  error: {
    main: '#d32f2f',            // text-on-white: 5.08:1 
    light: '#ef5350',           // text-on-white: 3.04:1 
  },
  warning: {
    main: '#ed6c02',            // text-on-white: 2.94:1  COMMON FAILURE
    light: '#ff9800',           // text-on-white: 2.02:1 
    dark: '#e65100',            // text-on-white: 3.84:1  (still fails!)
    contrastText: 'rgba(0, 0, 0, 0.87)',  // check on warning.main
  },
  info: {
    main: '#0288d1',            // text-on-white: 4.54:1  (barely)
    light: '#03a9f4',           // text-on-white: 2.88:1 
  },
  success: {
    main: '#2e7d32',            // text-on-white: 7.24:1 
    light: '#4caf50',           // text-on-white: 2.52:1 
  },
  text: {
    primary: 'rgba(0,0,0,0.87)',   // -> ~#212121: 16.07:1 on white 
    secondary: 'rgba(0,0,0,0.6)', // -> ~#666: 5.74:1 on white 
    disabled: 'rgba(0,0,0,0.38)', // -> ~#9E9E9E: 2.34:1  (exempt when disabled)
  },
  background: { paper: '#fff', default: '#fafafa' },
  action: {
    active: 'rgba(0,0,0,0.54)',   // ~4.48:1  for small icons
    disabled: 'rgba(0,0,0,0.26)', // exempt when disabled
  }
}

Chakra UI v2/v3

// Token paths in extendTheme()
const theme = extendTheme({
  colors: {
    // Direct palette values
    brand: { 50: '#f5f3ff', 500: '#7C3AED', 600: '#6D28D9', 700: '#5B21B6', 900: '#2E1065' },
    gray: { 50: '#F9FAFB', 100: '#F3F4F6', 200: '#E5E7EB', 300: '#D1D5DB',
            400: '#9CA3AF',  // text-on-white: 2.85:1 
            500: '#6B7280',  // text-on-white: 4.48:1  (near-miss)
            600: '#4B5563',  // text-on-white: 7.44:1 
            700: '#374151', 800: '#1F2937', 900: '#111827' },
  },
  semanticTokens: {
    colors: {
      'chakra-body-text': { default: 'gray.800', _dark: 'whiteAlpha.900' },
      'chakra-body-bg': { default: 'white', _dark: 'gray.800' },
      'chakra-placeholder-color': { default: 'gray.400', _dark: 'whiteAlpha.400' },
      // gray.400 on white = 2.85:1  - placeholder fails AA
    }
  },
  components: {
    Button: {
      variants: {
        solid: (props) => ({
          bg: `${props.colorScheme}.500`,  // check colorScheme.500 on white
          color: 'white',                  // white on colorScheme.500 - check 3:1
        }),
        ghost: (props) => ({
          color: `${props.colorScheme}.600`,  // text-on-white variant
        }),
      }
    }
  }
});

Style Dictionary (W3C Design Tokens)

{
  "color": {
    "text": {
      "primary": { "$value": "#111827", "$type": "color" },
      "secondary": { "$value": "#6B7280", "$type": "color" },   // 4.48:1 on white 
      "muted": { "$value": "#9CA3AF", "$type": "color" },       // 2.85:1 on white 
      "inverse": { "$value": "#FFFFFF", "$type": "color" },
      "on-primary": { "$value": "#FFFFFF", "$type": "color" }
    },
    "background": {
      "default": { "$value": "#FFFFFF", "$type": "color" },
      "subtle": { "$value": "#F9FAFB", "$type": "color" },
      "primary": { "$value": "#1D4ED8", "$type": "color" }
    },
    "status": {
      "error": { "$value": "#DC2626", "$type": "color" },
      "warning": { "$value": "#D97706", "$type": "color" },     // 3:1 on white  for normal text
      "success": { "$value": "#16A34A", "$type": "color" },
      "info": { "$value": "#2563EB", "$type": "color" }
    },
    "border": {
      "default": { "$value": "#D1D5DB", "$type": "color" },     // 1.44:1 on white  UI component
      "focus": { "$value": "#2563EB", "$type": "color" }        // focus ring
    }
  }
}

High-Risk Token Pairs - Known Failures

Token pairCommon valueRatio on whiteStatusNotes
MUI warning.main#ed6c022.94:1FAILOrange on white - always fails
MUI warning.light#ff98002.02:1FAILLight orange - critical failure
Tailwind amber-400#FBBF241.73:1FAILNever use amber-400 as text
Tailwind yellow-400#FACC151.60:1FAILYellow always fails on white
gray-400 (Tailwind)#9CA3AF2.85:1FAILCommon placeholder color
gray-500 (Tailwind)#6B72804.48:1FAILNear-miss - very common
Chakra gray.400#9CA3AF2.85:1FAILChakra placeholder default
MUI text.disabledrgba(0,0,0,0.38)~2.34:1(exempt)Disabled = exempt per WCAG
MUI action.activergba(0,0,0,0.54)~4.48:1FAILIcon color on white
shadcn --muted-foregroundhsl(215.4 16.3% 46.9%)~4.48:1FAILDefault shadcn theme
shadcn --destructivehsl(0 84.2% 60.2%)~3.13:1FAILRed badge on white
Style Dictionary text.secondary#6B72804.48:1FAILUbiquitous - always check

Compliant Replacements

Failing tokenReplacementNew ratioNotes
#9CA3AF (gray-400)#6B7280 (gray-500)4.48:1Still near-miss; use #595959 for safety
#6B7280 (gray-500)#4B5563 (gray-600)7.44:1Safest option
#ed6c02 (MUI warning)#b45309 (amber-700)4.57:1Minimum pass
#ff9800 (MUI warning.light)#b45309 (amber-700)4.57:1
#FBBF24 (amber-400)#92400e (amber-800)8.80:1Use as background, not text
#FACC15 (yellow-400)#713f12 (yellow-900)12.04:1Use as background, not text
hsl(0 84.2% 60.2%) (shadcn destructive)#b91c1c (red-700)5.56:1

Storybook addon-a11y Configuration

npm install --save-dev @storybook/addon-a11y
// .storybook/main.js
module.exports = {
  addons: ['@storybook/addon-a11y'],
};

// .storybook/preview.js - global configuration
export const parameters = {
  a11y: {
    config: {
      rules: [
        { id: 'color-contrast', enabled: true },
        { id: 'button-name', enabled: true },
        { id: 'image-alt', enabled: true },
        { id: 'focus-visible', enabled: true },    // Requires axe-core 4.4+
        { id: 'target-size', enabled: true },       // WCAG 2.5.5 / 2.5.8
      ],
    },
    // Disable for specific stories (use sparingly)
    disable: false,
  },
};

// Per-story override
export const MyStory = {
  parameters: {
    a11y: {
      config: {
        rules: [{ id: 'color-contrast', enabled: false }],  // Document WHY
      }
    }
  }
};

Running Storybook a11y Checks in CI

# Install storybook test runner
npm install --save-dev @storybook/test-runner

# package.json scripts
{
  "scripts": {
    "storybook:test": "test-storybook",
    "storybook:test:a11y": "test-storybook --ci"
  }
}

# Run in CI
npx storybook dev --port 6006 &
npx wait-on tcp:6006
npx test-storybook --ci

WCAG 2.4.13 Focus Appearance Requirements (AAA, exceeds AA baseline)

WCAG 2.4.13 Focus Appearance (Level AAA in WCAG 2.2) - exceeds the 2.4.7 Focus Visible (AA) baseline, but recommended as best practice:

  1. Area: Focus indicator encloses the component OR has a perimeter >= component's perimeter x 2px
  2. Contrast change: The focus indicator area must change contrast by >= 3:1 between focused and unfocused states
  3. Not obscured: The focus indicator must not be entirely hidden by author-created content

Minimum Compliant Focus Ring Implementation

/* Minimum WCAG 2.4.13 compliant focus ring */
:focus-visible {
  outline: 2px solid #0054B3;      /* >= 2px width */
  outline-offset: 2px;             /* Separates from component edge */
  /* #0054B3 on #FFF = 8.28:1 -> passes 3:1 for UI components */
}

/* Dark mode variant */
@media (prefers-color-scheme: dark) {
  :focus-visible {
    outline-color: #7CAFFF;        /* lighter blue on dark background */
    /* #7CAFFF on #1E1E1E = 5.74:1  */
  }
}

/* VIOLATION patterns to detect */
:focus { outline: none; }                           /* Hard fail */
:focus { outline: 0; }                              /* Hard fail */
:focus-visible { box-shadow: none; outline: none; } /* Hard fail */
button:focus { outline: none; }                     /* Hard fail */
*:focus { outline-color: transparent; }             /* Hard fail */

Focus Ring Token Validation Checklist

CheckRequirementTool
outline-width >= 2pxWCAG 2.4.13 area requirementCSS audit
Focus color contrast >= 3:1Against adjacent backgroundContrast calculator
Focus state differs from unfocusedVisible change requiredVisual inspection
No outline: none without replacementN/Agrep / CSS audit
Present in both light and dark modesConsistentVisual inspection

Design Token File Discovery Commands

# Find all token files in a project
find . -type f \( \
  -name "tokens.json" \
  -o -name "design-tokens.json" \
  -o -name "colors.json" \
  -o -name "variables.css" \
  -o -name "tokens.css" \
  -o -name "_variables.scss" \
  -o -name "theme.ts" \
  -o -name "theme.js" \
  -o -name "tailwind.config.*" \
\) \
-not -path "*/node_modules/*" \
-not -path "*/.next/*" \
-not -path "*/dist/*"

# PowerShell equivalent
Get-ChildItem -Recurse -File -Include tokens.json,design-tokens.json,colors.json,`
  variables.css,tokens.css,_variables.scss,theme.ts,theme.js,tailwind.config.js,tailwind.config.ts `
  | Where-Object { $_.FullName -notmatch 'node_modules|\.next|dist' }

Severity Classification

FindingSeverity
Text token below 3:1Critical
Text token 3:1-4.49:1 (normal text)Error
Text token 4.5:1-6.99:1, AAA targetWarning
UI component token below 3:1Error
Focus ring missing completelyCritical
Focus ring below 2pxError
Focus ring contrast below 3:1Error
Touch target token below 24 x 24px (WCAG 2.5.8)Error
Touch target token below 44 x 44px (WCAG 2.5.5)Warning
No prefers-reduced-motion resetWarning
Placeholder color below 4.5:1Error
Disabled token below 3:1Info (documented exemption, note for transparency)

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/community-access/accessibility-agents/design-system">View design-system on skillZs</a>