skills/color-contrast-auditor/SKILL.md
Detects and fixes color contrast violations using WCAG 2.1 guidelines and perceptual analysis. Expert in contrast ratio calculation, color blindness simulation, and providing accessible alternatives. Activate on "check contrast", "color accessibility", "WCAG audit", "readability check", "contrast ratio", "hard to read", "can't see text". NOT for general color theory (use color-theory-palette-harmony-expert), brand color selection (use web-design-expert), or non-visual accessibility (use ux-friction-analyzer).
npx skillsauth add curiositech/windags-skills color-contrast-auditorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Detects color contrast violations that make text unreadable and provides WCAG-compliant fixes. Uses both mathematical contrast ratio analysis and perceptual evaluation via vision capabilities.
Activate on:
NOT for:
web-design-expert)color-theory-palette-harmony-expert)| Text Type | Minimum Ratio | Example | |-----------|---------------|---------| | Normal text (<24px, <18.66px bold) | 4.5:1 | Body copy, labels, buttons | | Large text (≥24px or ≥18.66px bold) | 3:1 | Headlines, hero text | | UI components (borders, icons) | 3:1 | Form inputs, icons, focus rings | | Graphical objects | 3:1 | Charts, infographics |
| Text Type | Minimum Ratio | |-----------|---------------| | Normal text | 7:1 | | Large text | 4.5:1 |
| Element | Requirement | |---------|-------------| | Focus indicators | 3:1 against adjacent colors | | Form field borders | 3:1 against background | | Icons conveying meaning | 3:1 against background | | Disabled elements | No requirement (but consider UX) |
Contrast Ratio = (L1 + 0.05) / (L2 + 0.05)
Where L1 = lighter color's relative luminance
L2 = darker color's relative luminance
function relativeLuminance(r, g, b) {
// Convert 0-255 to 0-1
let [rs, gs, bs] = [r, g, b].map(c => c / 255);
// Apply gamma correction
const gamma = c => c <= 0.03928
? c / 12.92
: Math.pow((c + 0.055) / 1.055, 2.4);
const [R, G, B] = [rs, gs, bs].map(gamma);
// Weighted sum (human eye sensitivity)
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
function contrastRatio(color1, color2) {
const l1 = relativeLuminance(...color1);
const l2 = relativeLuminance(...color2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
❌ FAILING EXAMPLE (from user screenshot):
┌─────────────────────────────────────────────┐
│ Background: #F5F2E8 (beige/cream) │
│ Text: #C8FF00 (lime green) │
│ │
│ "Scan. Crawl. Match. Report." │
│ ← UNREADABLE │
│ │
│ Calculated Ratio: ~1.5:1 │
│ Required: 4.5:1 (normal) or 3:1 (large) │
│ Verdict: FAIL by 3x │
└─────────────────────────────────────────────┘
✅ FIXED OPTIONS:
• Darken text to #5A7300 → Ratio: 4.5:1
• Darken background to #2A2A2A → Ratio: 12:1
• Use dark green #1A4D00 → Ratio: 8:1
❌ COMMON FAILURE:
Background: #FFFFFF
Text: #AAAAAA (light gray)
Ratio: 2.3:1 ← FAIL
✅ FIXES:
• Text: #767676 → Ratio: 4.5:1 (minimum AA)
• Text: #595959 → Ratio: 7:1 (AAA)
❌ DECEPTIVE FAILURE:
Background: #FFF8E7 (warm white)
Text: #FF6B6B (coral/salmon)
Ratio: 2.8:1 ← FAIL (looks "colorful" but fails)
✅ FIXES:
• Text: #C62828 (darker red) → Ratio: 5.2:1
• Text: #8B0000 (dark red) → Ratio: 8.1:1
❌ "MINIMALIST" FAILURE:
Background: #FAFAFA
Text: #E0E0E0
Ratio: 1.3:1 ← SEVERELY FAILING
This is NOT minimalism. This is inaccessible.
✅ MINIMALIST + ACCESSIBLE:
Background: #FAFAFA
Text: #616161 → Ratio: 5.7:1
❌ COMMON FORM FAILURE:
Input background: #FFFFFF
Placeholder: #CCCCCC
Ratio: 1.6:1 ← FAIL
✅ FIX:
Placeholder: #757575 → Ratio: 4.6:1
❌ VARIABLE CONTRAST:
Gradient: #FFFFFF → #000080
Text: #FFFFFF (fixed)
Top of gradient: 1:1 (invisible!)
Bottom of gradient: 8.6:1 (good)
✅ SOLUTIONS:
• Add text shadow/outline
• Use semi-transparent overlay behind text
• Ensure ALL gradient stops pass contrast
When given a screenshot, identify:
Text elements by size:
Interactive elements:
Red flags to look for:
From CSS/code:
# Find all color declarations
grep -E "(color:|background:|#[0-9a-fA-F]{3,8}|rgb|hsl)" styles.css
From Tailwind:
# Find text/bg color classes
grep -E "(text-|bg-)" *.tsx *.jsx
For each text/background pair:
# Contrast Audit Report
## Summary
- Total color pairs tested: X
- Passing (AA): Y
- Failing: Z
- Critical failures (<2:1): N
## Failures by Severity
### Critical (Ratio < 2:1)
| Location | Foreground | Background | Ratio | Required | Fix |
|----------|------------|------------|-------|----------|-----|
| Hero tagline | #C8FF00 | #F5F2E8 | 1.5:1 | 3:1 | #5A7300 |
### Moderate (Ratio 2:1 - 3:1)
...
### Minor (Ratio 3:1 - 4.5:1, normal text only)
...
## Recommended Fixes
[Specific color replacements with new ratios]
Contrast requirements help but don't fully address color blindness. Additional checks:
| Type | Affected | Consideration | |------|----------|---------------| | Deuteranopia | 6% of males | Red/green confusion | | Protanopia | 2% of males | Red appears dark | | Tritanopia | <1% | Blue/yellow confusion |
Never rely on color alone for meaning
Test problematic pairs:
Use sufficient lightness difference
| Use Case | Color | Hex | Ratio | |----------|-------|-----|-------| | Body text | Dark gray | #333333 | 12.6:1 | | Secondary text | Medium gray | #767676 | 4.5:1 | | Links | Blue | #0066CC | 5.3:1 | | Success | Green | #2E7D32 | 5.1:1 | | Error | Red | #C62828 | 6.0:1 | | Warning | Orange-brown | #E65100 | 4.5:1 |
| Use Case | Color | Hex | Ratio | |----------|-------|-----|-------| | Body text | Light gray | #E0E0E0 | 13.4:1 | | Secondary | Medium gray | #9E9E9E | 6.3:1 | | Accent | Light blue | #90CAF9 | 7.3:1 |
| Use Case | Color | Hex | Ratio | |----------|-------|-----|-------| | Body text | Off-white | #F5F5F5 | 14.1:1 | | Secondary | Light gray | #BDBDBD | 8.3:1 |
// Chrome DevTools: Elements → Styles → hover color swatch
// Shows contrast ratio automatically
// Firefox: Accessibility Inspector
// Shows color contrast issues
// axe-core (popular a11y testing library)
const axe = require('axe-core');
axe.run(document, { rules: ['color-contrast'] });
// Lighthouse (built into Chrome)
// Performance → Accessibility → Color contrast
# Lighthouse CLI
npx lighthouse https://example.com --only-categories=accessibility
# Pa11y
npx pa11y https://example.com
When analyzing a screenshot or codebase:
For the example screenshot (lime on beige):
AUDIT RESULT: CRITICAL FAILURE
Element: Hero tagline "Scan. Crawl. Match. Report."
Foreground: ~#C8FF00 (lime green)
Background: ~#F5F2E8 (beige)
Calculated Ratio: ~1.5:1
Required (large text): 3:1
Required (normal text): 4.5:1
Status: ❌ FAILS BY 2-3x
RECOMMENDED FIXES:
1. Darken text to #5A7300 (olive) → 4.8:1 ✓
2. Darken text to #3D5C00 (dark olive) → 7.1:1 ✓✓
3. Keep lime, darken BG to #3D3D3D → 8.2:1 ✓✓
4. Use #1B5E20 (dark green) → 8.4:1 ✓✓
Before shipping:
Philosophy: Beautiful design and accessibility are not mutually exclusive. High contrast can be striking, dramatic, and intentional. Low contrast isn't "minimalist"—it's exclusionary. Every unreadable word is a user lost.
data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.