$npx -y skills add dylantarre/design-system-skills --skill color-contrastValidates WCAG 2.1 contrast ratios and generates accessible color pairings. Use when checking accessibility compliance, fixing contrast issues, or selecting text/background combinations for AA or AAA levels.
| 1 | # Color Contrast Checker |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Validate and generate accessible color combinations that meet WCAG 2.1 contrast requirements. Check existing palettes or find compliant alternatives for any color pair. |
| 6 | |
| 7 | ## When to Use |
| 8 | |
| 9 | - Validating a color palette for accessibility |
| 10 | - Finding accessible text colors for a background |
| 11 | - Checking if brand colors meet WCAG standards |
| 12 | - Generating accessible color pairings from tokens |
| 13 | |
| 14 | ## Quick Reference: WCAG Requirements |
| 15 | |
| 16 | | Level | Normal Text | Large Text | UI Components | |
| 17 | |-------|-------------|------------|---------------| |
| 18 | | AA | 4.5:1 | 3:1 | 3:1 | |
| 19 | | AAA | 7:1 | 4.5:1 | 4.5:1 | |
| 20 | |
| 21 | **Large text**: 18pt (24px) regular or 14pt (18.5px) bold |
| 22 | |
| 23 | ## The Process |
| 24 | |
| 25 | 1. **Get colors**: Ask for foreground and background colors (hex, rgb, oklch, hsl) |
| 26 | 2. **Calculate ratio**: Compute relative luminance and contrast ratio |
| 27 | 3. **Report compliance**: Show AA/AAA pass/fail for text sizes |
| 28 | 4. **If failing**: Suggest closest compliant alternatives |
| 29 | 5. **Batch mode**: If given a palette, check all combinations |
| 30 | |
| 31 | ## Contrast Ratio Scale |
| 32 | |
| 33 | | Ratio | Rating | Suitable For | |
| 34 | |-------|--------|--------------| |
| 35 | | 21:1 | Maximum | Black on white | |
| 36 | | 7:1+ | Excellent | AAA all text | |
| 37 | | 4.5:1+ | Good | AA normal text, AAA large | |
| 38 | | 3:1+ | Minimum | AA large text, UI components | |
| 39 | | < 3:1 | Failing | Decorative only | |
| 40 | |
| 41 | ## Output Formats |
| 42 | |
| 43 | **Single Pair Check:** |
| 44 | ``` |
| 45 | Contrast Check: #1a1a1a on #ffffff |
| 46 | |
| 47 | Ratio: 16.1:1 |
| 48 | |
| 49 | ✓ AA Normal Text (4.5:1) |
| 50 | ✓ AA Large Text (3:1) |
| 51 | ✓ AAA Normal Text (7:1) |
| 52 | ✓ AAA Large Text (4.5:1) |
| 53 | ✓ UI Components (3:1) |
| 54 | |
| 55 | Verdict: Passes all WCAG 2.1 criteria |
| 56 | ``` |
| 57 | |
| 58 | **Failing Pair with Suggestions:** |
| 59 | ``` |
| 60 | Contrast Check: #6b7280 on #ffffff |
| 61 | |
| 62 | Ratio: 4.0:1 |
| 63 | |
| 64 | ✓ AA Large Text (3:1) |
| 65 | ✗ AA Normal Text (4.5:1) - needs 4.5:1 |
| 66 | ✗ AAA Normal Text (7:1) |
| 67 | ✓ UI Components (3:1) |
| 68 | |
| 69 | Suggested fixes: |
| 70 | - Darken foreground to #5b6370 for AA (4.5:1) |
| 71 | - Darken foreground to #3d4351 for AAA (7:1) |
| 72 | ``` |
| 73 | |
| 74 | **Palette Matrix:** |
| 75 | ``` |
| 76 | Color Contrast Matrix (AA Normal Text = 4.5:1) |
| 77 | |
| 78 | white gray-100 gray-900 primary |
| 79 | white - 1.1 16.1 5.2 |
| 80 | gray-100 1.1 - 14.5 4.7 |
| 81 | gray-900 16.1 14.5 - 3.1 |
| 82 | primary 5.2 4.7 3.1 - |
| 83 | |
| 84 | Legend: ✓ = 4.5:1+ | ⚠ = 3:1-4.5:1 | ✗ = <3:1 |
| 85 | ``` |
| 86 | |
| 87 | **CSS with Accessible Pairs:** |
| 88 | ```css |
| 89 | :root { |
| 90 | /* Accessible text on light backgrounds */ |
| 91 | --text-on-light: #1f2937; /* 14.5:1 on white */ |
| 92 | --text-muted-on-light: #4b5563; /* 7.2:1 on white */ |
| 93 | |
| 94 | /* Accessible text on dark backgrounds */ |
| 95 | --text-on-dark: #f9fafb; /* 18.1:1 on gray-900 */ |
| 96 | --text-muted-on-dark: #d1d5db; /* 11.3:1 on gray-900 */ |
| 97 | |
| 98 | /* Accessible text on brand color */ |
| 99 | --text-on-primary: #ffffff; /* 5.2:1 on primary-500 */ |
| 100 | } |
| 101 | ``` |
| 102 | |
| 103 | ## Algorithm |
| 104 | |
| 105 | **Relative Luminance (L):** |
| 106 | ``` |
| 107 | For each RGB channel (0-255): |
| 108 | 1. Normalize: value / 255 |
| 109 | 2. If ≤ 0.03928: channel / 12.92 |
| 110 | 3. Else: ((channel + 0.055) / 1.055) ^ 2.4 |
| 111 | |
| 112 | L = 0.2126 * R + 0.7152 * G + 0.0722 * B |
| 113 | ``` |
| 114 | |
| 115 | **Contrast Ratio:** |
| 116 | ``` |
| 117 | ratio = (L1 + 0.05) / (L2 + 0.05) |
| 118 | where L1 is lighter, L2 is darker |
| 119 | ``` |
| 120 | |
| 121 | ## Common Patterns |
| 122 | |
| 123 | **Accessible grays on white:** |
| 124 | | Gray | Hex | Ratio | Use | |
| 125 | |------|-----|-------|-----| |
| 126 | | 600 | #4b5563 | 7.2:1 | Body text (AAA) | |
| 127 | | 500 | #6b7280 | 4.6:1 | Body text (AA) | |
| 128 | | 400 | #9ca3af | 2.9:1 | Large text only | |
| 129 | |
| 130 | **Accessible brand colors:** |
| 131 | - Light backgrounds: Darken brand to 500-700 range |
| 132 | - Dark backgrounds: Lighten brand to 300-400 range |
| 133 | - On brand: Use white (dark brands) or gray-900 (light brands) |
| 134 | |
| 135 | ## Integration with Color Scale |
| 136 | |
| 137 | When using with the color-scale skill: |
| 138 | |
| 139 | ``` |
| 140 | 1. Generate palette with color-scale |
| 141 | 2. Run contrast check on key combinations: |
| 142 | - Text colors (900, 800) on backgrounds (50, 100) |
| 143 | - Background (500) with white/dark text |
| 144 | - Adjacent scale steps for borders |
| 145 | 3. Adjust chroma/lightness if needed |
| 146 | 4. Document accessible pairings |
| 147 | ``` |
| 148 | |
| 149 | ## Testing Checklist |
| 150 | |
| 151 | - [ ] Primary text on all background colors |
| 152 | - [ ] Secondary/muted text on backgrounds |
| 153 | - [ ] Interactive elements (buttons, links) |
| 154 | - [ ] Focus indicators against backgrounds |
| 155 | - [ ] Error, warning, success states |
| 156 | - [ ] Disabled states (3:1 minimum for discernible) |
| 157 | - [ ] Placeholder text in inputs |