mgifford/accessibility-skills/color-contrast

Claude Code and agent skill by mgifford/accessibility-skills

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

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.

Install

npx vskill@latest install mgifford/accessibility-skills/color-contrast

SKILL.md

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. Contrast depends on the colors actually adjacent after opacity, gradients, images, overlays, states, and themes are applied — a passing palette is not proof the finished interface passes.

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)

LevelMeaning
CriticalContrast failure makes content or interaction completely inaccessible
SeriousContrast failure significantly impairs access for a disability group
ModerateContrast issue degrades usability but content remains partially accessible
MinorBest-practice gap; marginal impact

Prioritize by user impact when time is limited: (1) text/controls required to complete a task, (2) keyboard focus indicators and component states, (3) errors/warnings/required fields/status messages, (4) navigation/links/labels, (5) charts and information-bearing graphics, (6) supporting/secondary content.


Critical: Text Contrast (WCAG 1.4.3)

Text typeMinimum (AA)Enhanced (AAA)
Normal text (below 18pt / 14pt bold)4.5:17:1
Large text (18pt+ or 14pt+ bold)3:14.5:1
Logotypes / purely decorative textExemptExempt
Disabled controlsExemptExempt

"Large text" means 18pt (≈ 24 CSS px) or larger at normal weight, or 14pt (≈ 18.67 CSS px) or larger in bold. WCAG thresholds are pass/fail — do not round 4.499:1 up to 4.5:1. Treat thin or unusual typefaces cautiously — a combination can meet the computed threshold but stay hard to read due to narrow strokes.

The requirement covers body text, headings, link text, button/form labels, placeholder text, text revealed on hover/focus, validation/status messages, and text rendered as an image. Do not extend the logotype/decorative exception to brand colors used for ordinary content.

If CSS specifies only a foreground or only a background, the other side may come from an unknown user preference — always specify both sides of a color pair:

css
html { background: #ffffff; color: #1b1f23; }
.notice { background: #f2f6fa; color: #243447; }
css
/* Bad — fails 1.4.3 */
.placeholder { color: #aaaaaa; }
.note { color: #888; }

Prefer real text over images of text (WCAG 1.4.5) — it reflows, resizes, and adapts to themes; use an image of text only when the presentation is customizable or essential.


Serious: Non-text Contrast (WCAG 1.4.11)

Visual information needed to identify a UI component or its state, and parts of a graphic needed to understand it, need 3:1 contrast against adjacent colors. Examples: a checkbox boundary that identifies the control, a check mark communicating checked state, an icon in an icon-only button, a slider thumb/track, an error boundary, and chart lines/sectors/symbols needed to read it.

This does not mean every decorative border must hit 3:1 — if text, shape, position, or another treatment already identifies the component/state, a low-contrast decorative border isn't necessarily the required information. Does not apply to: purely decorative graphics, inactive/disabled components, logos/brand marks, or graphics supplementary to adjacent text.

Native controls are a strong default — browsers/OS adapt them to user preferences; replacing them creates responsibility for every state and mode:

css
.checkbox {
appearance: none;
inline-size: 1.25rem;
block-size: 1.25rem;
border: 2px solid #59636e; /* test against surrounding background */
border-radius: 0.2rem;
}
.checkbox:checked {
border-color: #005ea8;
background-color: #005ea8;
}

Test the unchecked boundary against its background, the checked fill against the surrounding background, AND the check mark against the checked fill — do not rely on a code comment claiming a pair passes; measure the rendered combination. Use currentColor for icon strokes so they adapt with the button's text color.


Serious: Use of Color (WCAG 1.4.1)

Color alone must not be the sole means of conveying information, prompting a response, or distinguishing an element. Add a visible text, shape, pattern, icon, or other non-color cue whenever color carries meaning.

html
<!-- Bad: required/error indicated only by color -->
<label style="color: red;">Email address</label>
<input type="email" style="border-color: red;">
<!-- Good: icon + text + color + aria-invalid -->
<div class="field" data-state="error">
<label for="email">Email address <span aria-hidden="true">(required)</span></label>
<input id="email" type="email" required aria-invalid="true" aria-describedby="email-error">
<p id="email-error" class="error-message">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
<path d="M12 3 2 21h20L12 3Zm0 6v5m0 3v.01"></path>
</svg>
Enter an email address in the format name@example.com.
</p>
</div>

Links in body text must be distinguishable from surrounding text by more than color — underlining is the simplest dependable pattern:

css
a {
color: #005ea8;
text-decoration-line: underline;
text-decoration-thickness: max(1px, 0.08em);
}

A documented exception (WCAG technique G183) permits links without a default underline when link text differs from surrounding text by ≥3:1 and gains a non-color cue on hover/focus — this is harder to maintain; prefer persistent underlines in prose. Charts/status indicators need labels, symbols, line styles, or patterns in addition to color — a separate text-equivalent table does not excuse the graphic itself from contrast requirements.


Serious: Keyboard Focus Appearance

WCAG 2.2 AA requires a visible focus indicator (2.4.7). 2.4.13 Focus Appearance is Level AAA — do not present its area/contrast-change formula as an AA requirement. WCAG 1.4.11 may still apply to an author-created focus indicator as the visual information identifying the focused state.

css
/* Start with the browser default; don't remove without a tested replacement */
:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 3px;
}
/* Two-color indicator — more robust across light/dark surfaces */
:focus-visible {
outline: 3px solid #ffffff;
outline-offset: 2px;
box-shadow: 0 0 0 5px #111111;
}
@media (forced-colors: active) {
:focus-visible {
outline: 3px solid Highlight;
box-shadow: none;
}
}
css
/* NEVER do this without a tested replacement */
:focus { outline: none; }

Do not add :focus:not(:focus-visible) { outline: none; } as a universal rule — browsers already determine when :focus-visible matches, and a blanket suppression is risky. Test the indicator's geometry, clipping, and adjacent colors on every surface and component state.


Serious: Forced-Colors Mode

Windows High Contrast Mode / forced-colors replaces author colors with system colors. Interfaces break when background-color, box-shadow, or color are the sole means of conveying meaning — outline survives forced-colors; box-shadow may be suppressed.

css
@media (forced-colors: active) {
.status-icon, .icon-button svg { fill: none; stroke: currentColor; }
[aria-current="page"] { border-block-end: 0.25rem solid currentColor; }
[aria-invalid="true"] { border: 3px solid Mark; }
}

Use native controls and semantic HTML first; add targeted corrections only where testing shows information disappears. Avoid forced-color-adjust: none unless preserving author colors is essential and tested against multiple user-selected system palettes — it opts the element out of the mode's protections.

Testing: enable Windows High Contrast in Accessibility settings; Chrome DevTools → Rendering → "Emulate CSS media feature forced-colors: active"; Firefox about:config → ui.forcedColors: 1.


Moderate: Themes and Semantic Color Tokens

Validate complete foreground-background pairs, not isolated colors. Semantic tokens make intended pairings explicit:

css
:root {
color-scheme: light dark;
--surface: light-dark(#ffffff, #15191e);
--text: light-dark(#1b1f23, #f4f6f8);
--link: light-dark(#005ea8, #73b7f2);
--control-border: light-dark(#59636e, #a7b0ba);
}
html { background: var(--surface); color: var(--text); }
.card { background: var(--surface); color: var(--text); border: 1px solid var(--control-border); }

light-dark() is useful when the project's browser support policy permits it; provide a custom-property fallback for older browsers. See skills/light-dark-mode/SKILL.md for a manual theme-selector implementation.

Do not assume CSS color functions guarantee accessibility — color-mix() creates a color between two inputs but doesn't target a WCAG ratio; contrast-color() picks from a limited result set in current implementations and doesn't replace validating text/component/state requirements. Treat both as implementation aids, not conformance tests.

css
/* Bad: hard-coded value that may fail in dark mode */
.card { background-color: #ffffff; color: #333; }

Serious: Variable Backgrounds, Opacity, and Overlays

For text over a gradient, image, video, or translucent surface, test the least favorable rendered region that can occur in normal use. Use an opaque or sufficiently opaque backing surface when the underlying content can't be controlled:

css
.hero-title {
display: inline;
padding: 0.15em 0.35em;
color: #ffffff;
background: #16202a;
box-decoration-break: clone;
}

Be careful with opacity on a parent element — it blends text and background with whatever is behind the component, changing both sides of the contrast pair:

css
/* Avoid: reduces opacity of the whole component */
.secondary-card { opacity: 0.6; }
/* Prefer: explicit, validated colors */
.secondary-card { background: #f2f6fa; color: #4d5966; }

Automated tools may report variable backgrounds as "incomplete" — that's a limitation of the test, not evidence the content passes.


Moderate: States That Need Separate Validation

Check each component in every state users can encounter: default, hover, keyboard focus, active/pressed, selected/checked, visited, invalid/error, success/warning/informational, disabled/inactive, browser autofill, text selection, and open/expanded/current. Repeat across every supported theme.

Disabled controls are excepted from 1.4.3/1.4.11's specific ratios, but the exception is not a design target: keep disabled controls identifiable when practical; do not communicate the disabled state by color alone; explain why a control is unavailable when needed; consider whether disabling is necessary at all (a submit attempt with clear validation can be easier to understand than an unexplained unavailable button).

html
<button type="submit" disabled aria-describedby="submit-help">Submit</button>
<p id="submit-help">Add at least one contact before submitting.</p>

Moderate: User Contrast Preferences

prefers-contrast and forced-colors address different situations — neither is a light/dark theme selector. Use prefers-contrast: more as an enhancement; the default presentation must already meet the conformance target:

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/mgifford/accessibility-skills/color-contrast/badge)](https://verified-skill.com/skills/mgifford/accessibility-skills/color-contrast)

Something wrong with this skill or its page? Tell us.