| name | color-contrast |
| description | Load this skill whenever the project contains text, UI components, icons, form controls, data visualisations, or focus indicators — in short, almost every project. Under no circumstances hard-code colour values without verifying contrast ratios. Absolutely always ensure text meets 4.5:1, large text meets 3:1, and non-text UI elements meet 3:1 against adjacent colours. Test in light mode, dark mode, and forced-colors (high contrast) mode.
|
Color Contrast Accessibility Skill
Canonical source: examples/COLOR_CONTRAST_ACCESSIBILITY_BEST_PRACTICES.md in mgifford/ACCESSIBILITY.md
This skill is derived from that file. When in doubt, the example is authoritative.
Apply these rules whenever implementing or reviewing colour choices in HTML, CSS,
SVG, or any visual interface element.
Core Mandate
Sufficient contrast between foreground and background colors is a prerequisite
for users to read text, identify UI components, perceive graphical content, and
track keyboard focus. Color alone must never be the sole means of conveying
information.
All visual interface elements that convey information or require user interaction
must meet WCAG 2.2 Level AA contrast thresholds in light mode, dark mode, and
forced-colors (high contrast) mode.
Severity Scale (this skill)
| Level | Meaning |
|---|
| Critical | Contrast failure makes content or interaction completely inaccessible |
| Serious | Contrast failure significantly impairs access for a disability group |
| Moderate | Contrast issue degrades usability but content remains partially accessible |
| Minor | Best-practice gap; marginal impact |
Critical: Text Contrast (WCAG 1.4.3)
Normal text and images of text must meet these minimums:
| Text type | Minimum (AA) | Enhanced (AAA) |
|---|
| Normal text (below 18pt / 14pt bold) | 4.5:1 | 7:1 |
| Large text (18pt+ or 14pt+ bold) | 3:1 | 4.5:1 |
| Logotypes / purely decorative text | Exempt | Exempt |
| Disabled controls | Exempt | Exempt |
"Large text" means 18pt (≈ 24 CSS px) or larger in regular weight, or
14pt (≈ 18.67 CSS px) or larger in bold weight.
Preferred CSS pattern — text colours via custom properties
:root {
--color-text: #1a1a1a;
--color-text-muted: #595959;
--color-heading: #333333;
--color-link: #0066cc;
--color-background: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #e8e8e8;
--color-text-muted: #b0b0b0;
--color-link: #66aaff;
--color-background: #1a1a1a;
}
}
Avoid
.placeholder { color: #aaaaaa; }
.note { color: #888; }
Serious: Non-text Contrast (WCAG 1.4.11)
UI components and graphical objects required to understand or operate the
interface must have 3:1 contrast against adjacent colours.
Applies to
- Form input borders (text fields, checkboxes, radio buttons, selects)
- Interactive component boundaries (buttons without text, sliders, toggles)
- Icons and graphical objects that convey meaning
- Charts and data visualisation elements that encode information
- Status indicators (progress bars, meter fills)
Does not apply to
- Decorative graphics that convey no meaning
- Inactive / disabled components
- Logos and brand marks
- Graphical elements supplementary to adjacent text
Form control pattern
input[type="checkbox"] {
--checkbox-border: #767676;
appearance: none;
width: 1.25rem;
height: 1.25rem;
border: 2px solid var(--checkbox-border);
border-radius: 3px;
}
Serious: Use of Color (WCAG 1.4.1)
Color alone must not be the sole means of conveying information, indicating an
action, prompting a response, or distinguishing a visual element. A second,
non-color cue must always accompany color.
Common failure patterns
<label style="color: red;">Email address</label>
<input type="email">
<input type="email" style="border-color: red;">
Preferred patterns
<label>
Email address
<span aria-hidden="true" class="required-marker">*</span>
<span class="sr-only">(required)</span>
</label>
<input type="email" aria-required="true">
<div class="field field--error">
<label for="email">Email address</label>
<input id="email" type="email"
aria-describedby="email-error" aria-invalid="true">
<p id="email-error" class="error-message">
<svg role="img" aria-label="Error" aria-hidden="true">
<use href="#icon-exclamation"></use>
</svg>
Please enter a valid email address.
</p>
</div>
Link distinction from surrounding text
Links within body text must be distinguishable from surrounding text by more
than color alone. Use underline (the browser default) or another non-color cue.
a {
color: #0066cc;
text-decoration: underline;
}
Serious: Focus Appearance (WCAG 2.4.13)
WCAG 2.2 2.4.13 Focus Appearance (Level AAA) requires visible keyboard focus
indicators that:
- Enclose the focused component with an area of at least the perimeter × 2 CSS px in thickness.
- Have 3:1 contrast between focused and unfocused states.
- Have 3:1 contrast against every adjacent color in the unfocused state.
Preferred CSS pattern
:root {
--focus-ring-color: #0066cc;
--focus-ring-width: 3px;
--focus-ring-offset: 2px;
}
:focus-visible {
outline: var(--focus-ring-width) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
box-shadow: 0 0 0 calc(var(--focus-ring-width) + var(--focus-ring-offset))
#ffffff;
}
@media (prefers-color-scheme: dark) {
:root {
--focus-ring-color: #99ccff;
}
}
C40 two-color focus indicator
:focus-visible {
outline: 3px solid #000000;
outline-offset: 1px;
box-shadow: 0 0 0 5px #ffffff;
}
Avoid
:focus { outline: none; }
*:focus { outline: 0 !important; }
Serious: Forced-Colors Mode (WCAG 1.4.3, 1.4.11)
Windows High Contrast Mode and forced-colors replace author colors with
system colors. Interfaces break when CSS background-color, box-shadow, or
color properties are the sole means of conveying meaning.
Use outline for focus rings — it survives forced-colors
:focus-visible {
outline: 3px solid Highlight;
outline-offset: 2px;
}
:focus-visible {
box-shadow: 0 0 0 3px #0066cc;
}
Restore lost meaning in forced-colors mode
@media (forced-colors: active) {
.button {
background-color: ButtonFace;
color: ButtonText;
border: 2px solid ButtonBorder;
}
.icon {
forced-color-adjust: auto;
}
}
Testing forced-colors mode
- Enable High Contrast Mode in Windows Accessibility settings
- Chrome DevTools → Rendering → "Emulate CSS media feature forced-colors: active"
- Firefox:
about:config → ui.forcedColors: 1
Moderate: Semantic Color Token Pattern
Centralizing all design-system colors as CSS custom properties makes contrast
validation and theming manageable at scale.
:root {
--color-neutral-600: #595959;
--color-neutral-900: #1a1a1a;
--color-brand-500: #0066cc;
--color-brand-700: #004c99;
--color-text-primary: var(--color-neutral-900);
--color-text-secondary: var(--color-neutral-600);
--color-text-link: var(--color-brand-500);
--color-surface: #ffffff;
--color-border: #e8e8e8;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text-primary: #e8e8e8;
--color-text-secondary: #a0a0a0;
--color-text-link: #66aaff;
--color-surface: #1a1a1a;
--color-border: #444444;
}
}
.card {
background-color: var(--color-surface);
color: var(--color-text-primary);
border: 1px solid var(--color-border);
}
.card {
background-color: #ffffff;
color: #333;
}
Minor: APCA — Emerging Standard
The Advanced Perceptual Contrast Algorithm (APCA) is a candidate replacement
for the WCAG 2.x contrast ratio formula expected in WCAG 3.0. It models contrast
perception more accurately for thin strokes, small font sizes, and saturated colors.
APCA is not yet required. Teams adopting it today must continue to meet
WCAG 2.2 AA requirements in parallel.
| Content type | Minimum Lc | Recommended Lc |
|---|
| Normal body text (16px / 400 weight) | 60 | 75 |
| Large heading text (24px+ / 700 weight) | 45 | 60 |
| UI component labels | 45 | 60 |
| Placeholder / muted text | 30 | 45 |
Recommended Contrast-Checking Tools
Testing programmatically with axe-core
const axe = require("axe-core");
axe.run(document, {
runOnly: {
type: "rule",
values: ["color-contrast", "color-contrast-enhanced"]
}
}, (err, results) => {
if (err) throw err;
console.log("Contrast violations:", results.violations);
});
Definition of Done Checklist
Key WCAG Criteria
- 1.4.1 Use of Color (A) — Serious if failing
- 1.4.3 Contrast Minimum (AA) — Critical if failing for normal text
- 1.4.6 Contrast Enhanced (AAA)
- 1.4.11 Non-text Contrast (AA) — Serious if failing
- 2.4.7 Focus Visible (AA)
- 2.4.13 Focus Appearance (AAA, WCAG 2.2) — Serious if failing
References
Standards horizon: WCAG 3.0's proposed APCA (Advanced Perceptual
Contrast Algorithm) will replace the current luminance-ratio model with a
perceptual contrast model that treats light-on-dark and dark-on-light
differently. Do not apply APCA to production work until WCAG 3.0 is a
published standard. Monitor: https://www.w3.org/TR/wcag-3.0/ and
https://git.apcacontrast.com/