Design accessible color systems using WCAG contrast ratios and APCA, simulate color blindness, build dark mode palettes, and select tooling for contrast validation.
A skill is a written procedure a bot loads when a job calls for it. This one is a single file, SKILL.md, and the whole file is on this page.
You are an expert in accessible color design, specializing in contrast ratios, color blindness simulation, dark mode accessibility, and building design system color tokens that pass WCAG requirements automatically. You understand both the traditional WCAG 2.x contrast algorithm and the newer APCA (Accessible Perceptual Contrast Algorithm) approach.
When to Use
Use this skill when:
User asks about color contrast designer techniques or best practices
Design accessible color systems using WCAG contrast ratios and APCA, simulate color blindness, build dark mode palettes, and select tooling for contrast validation.
How much does it cost?+
Nothing. It ships with Murage, which is open source. Your bots run on the AI plan you already have.
Can I change it?+
Yes. Open it in Settings → Skills to read, edit or duplicate it. Skill Guard scans it again after every edit.
Give your first job to Murage.
Download the app, connect the AI you already pay for, and tell your Chief of Staff what needs doing. Plan on about ten minutes from install to a working team.
What it teaches a bot, what it covers, and the whole file to read.
The principle
“
You are an expert in accessible color design, specializing in contrast ratios, color blindness simulation, dark mode accessibility, and building design system color tokens that pass WCAG requirements automatically. You understand both the traditional WCAG 2.x contrast algorithm and the newer APCA (Accessible Perceptual Contrast Algorithm) approach.
Note: "18pt" in CSS is approximately 24px. "14pt bold" is approximately 18.66px bold.
The WCAG 2.x Contrast Formula
The WCAG 2 algorithm compares relative luminance:
Contrast Ratio = (L1 + 0.05) / (L2 + 0.05)
where L1 is the lighter color's relative luminance
and L2 is the darker color's relative luminance
Relative luminance calculation:
javascript
function relativeLuminance(r, g, b) {
const [rL, gL, bL] = [r, g, b].map(c => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * rL + 0.7152 * gL + 0.0722 * bL;
}
function contrastRatio(rgb1, rgb2) {
const l1 = relativeLuminance(...rgb1);
const l2 = relativeLuminance(...rgb2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// Example: white on dark blue
console.log(contrastRatio([255, 255, 255], [0, 51, 102]));
// Result: 11.07:1 -- passes AAA
Known Limitations of WCAG 2.x Contrast
The WCAG 2 formula has documented issues:
Treats all colors equally, but human perception is not uniform (blue text on black appears less readable than the ratio suggests)
Over-penalizes dark mode backgrounds
Does not account for font weight or size precisely
A ratio of 4.5:1 can be achieved by combinations that are perceptually poor
APCA (Accessible Perceptual Contrast Algorithm)
APCA is the contrast algorithm being developed for WCAG 3.0. It is more perceptually accurate than WCAG 2.x.
Key Differences from WCAG 2.x
Feature
WCAG 2.x
APCA
Polarity
Same ratio for light-on-dark and dark-on-light
Different values; polarity matters
Output
Ratio (e.g., 4.5:1)
Lightness Contrast value (Lc, e.g., Lc 60)
Font sensitivity
Two thresholds (large/normal)
Lookup table by font size and weight
Dark mode
Same rules
Better calibrated for dark backgrounds
APCA Minimum Contrast Lookup
Font Size
Weight 400
Weight 700
14px
Lc 90
Lc 75
16px
Lc 80
Lc 70
18px
Lc 75
Lc 60
24px
Lc 60
Lc 50
32px
Lc 55
Lc 45
48px
Lc 45
Lc 40
Higher Lc values mean more contrast. Body text at 16px weight 400 needs approximately Lc 75-80.
javascript
// APCA contrast calculation using the apca-w3 package
// add the package dependency apca-w3
import { APCAcontrast, sRGBtoY } from 'apca-w3';
const textY = sRGBtoY([68, 68, 68]); // #444444
const bgY = sRGBtoY([255, 255, 255]); // #ffffff
const Lc = APCAcontrast(textY, bgY);
console.log('APCA Lc:', Lc);
// Result: approximately Lc 83 -- good for body text
When to Use APCA vs WCAG 2.x
Use WCAG 2.x for compliance testing today (legally referenced standard)
Use APCA for design decisions, especially dark mode palettes
Use both to find colors that pass under both algorithms
APCA is expected to become the official algorithm in WCAG 3.0
Color Blindness Design
Types and Prevalence
Type
Affects
Prevalence (Male)
Prevalence (Female)
Colors Confused
Deuteranopia
Green cones
~5%
~0.4%
Red and green
Protanopia
Red cones
~1.3%
~0.02%
Red and green, red appears darker
Tritanopia
Blue cones
~0.01%
~0.01%
Blue and yellow
Achromatopsia
All cones
~0.003%
~0.003%
No color perception
Approximately 8% of men and 0.5% of women have some form of color vision deficiency. For 1,000 users, expect around 40 with color blindness.
Design Strategies
Never Rely on Color Alone (WCAG 1.4.1)
html
<!-- BAD: Status conveyed only by color -->
<span style="color: red;">Error</span>
<span style="color: green;">Success</span>
<!-- GOOD: Color plus icon plus text -->
<span class="status-error">
<svg aria-hidden="true"><!-- X icon --></svg>
Error: Invalid email address
</span>
<span class="status-success">
<svg aria-hidden="true"><!-- checkmark icon --></svg>
Success: Account created
</span>
Charts and Data Visualization
javascript
// BAD: Distinguishing data series only by color
const colors = ['#ff0000', '#00ff00', '#0000ff'];
// GOOD: Color plus pattern plus shape plus label
const series = [
{ color: '#d32f2f', pattern: 'solid', marker: 'circle', label: 'Revenue' },
{ color: '#1976d2', pattern: 'dashed', marker: 'square', label: 'Expenses' },
{ color: '#388e3c', pattern: 'dotted', marker: 'triangle', label: 'Profit' },
];
// Direct labeling is the best option -- place labels directly
// on or next to each line, eliminating the need for a legend
Form Validation
css
/* BAD: Error indicated only by red border */
.input-error {
border-color: red;
}
/* GOOD: Error indicated by border plus icon plus message */
.input-error {
border-color: #d32f2f;
border-width: 2px;
/* 3:1 contrast against background */
}
.error-message {
color: #b71c1c;
/* Plus visible error text explaining the issue */
}
Color Blindness Safe Palettes
Colors that remain distinguishable under all types of color vision (based on Wong, 2011):
Name
Hex
Use Case
Black
#000000
Text, borders
Orange
#E69F00
Warning, highlight
Sky Blue
#56B4E9
Information, link
Bluish Green
#009E73
Success
Yellow
#F0E442
Caution (with dark text)
Blue
#0072B2
Primary action
Vermillion
#D55E00
Error, destructive
Reddish Purple
#CC79A7
Accent
Dark Mode Design
Challenges Unique to Dark Mode
WCAG 2.x ratios can be misleading -- 4.5:1 white on dark gray can be harder to read than the ratio suggests due to halation (bright text blooms on dark backgrounds)
Saturation appears higher on dark backgrounds -- reduce saturation for dark mode
Depth reversal -- shadows do not work the same; use elevation with lighter surfaces
User fatigue -- pure white (#ffffff) on pure black (#000000) causes eye strain
Dark Mode Best Practices
css
/* Light mode tokens */
:root {
--surface-primary: #ffffff;
--surface-secondary: #f5f5f5;
--text-primary: #1a1a1a;
--text-secondary: #555555;
--text-muted: #767676;
--brand-primary: #0055cc;
--error: #c62828;
--success: #2e7d32;
}
/* Dark mode tokens -- not simply inverting light mode */
@media (prefers-color-scheme: dark) {
:root {
--surface-primary: #121212;
--surface-secondary: #1e1e1e;
--text-primary: #e0e0e0; /* Not pure white to reduce halation */
--text-secondary: #aaaaaa;
--text-muted: #888888;
--brand-primary: #6fa8ff; /* Desaturated, lighter for dark bg */
--error: #ff6b6b;
--success: #66bb6a;
}
}
Key rules:
Use #121212 or #1a1a1a for the darkest background, not pure #000000
Use #e0e0e0 or #eeeeee for primary text, not pure #ffffff
Desaturate brand colors by 10-20% for dark backgrounds
Increase elevation brightness (each dp of elevation adds 5% white overlay)
// Define primitive color scale
const blue = {
50: '#e3f2fd',
100: '#bbdefb',
200: '#90caf9',
300: '#64b5f6',
400: '#42a5f5',
500: '#2196f3',
600: '#1e88e5',
700: '#1565c0',
800: '#0d47a1',
900: '#0a2472',
};
// Map to semantic tokens with contrast guarantees
const lightMode = {
'color-text-primary': '#1a1a1a', // 13.5:1 on white
'color-text-link': blue[800], // 7.0:1 on white
'color-text-on-primary': '#ffffff', // Check against primary bg
'color-bg-primary': blue[700], // 4.5:1 with white text
'color-bg-error': '#fde8e8', // Background only, text is dark
'color-border-error': '#c62828', // 3:1 against white bg
};