We believe accessibility is a subset of quality. contrast-plus is a public-facing web tool to help designers, developers, and accessibility practitioners explore and understand color contrast. We commit to WCAG 2.2 AA standards for the application itself and track our progress publicly to remain accountable to our users.
| Metric | Status / Value |
|---|---|
| Open A11y Issues | View open accessibility issues |
| Automated Test Pass Rate | Monitored via axe scan in CI (view workflow) |
| A11y PRs Merged (MTD) | Tracked in project insights |
| Browser Support | Last 2 major versions of Chrome, Firefox, Safari |
To contribute to this repo, you must follow these guidelines:
- Semantic HTML: Use semantic landmarks and heading structure (one
<h1>per page). - Keyboard operability: All interactive elements must be keyboard operable with visible focus indicators.
- Labels and instructions: All inputs must have programmatic labels. Units and context must be clearly explained.
- Dynamic updates: Contrast result changes must be announced via
aria-liveorrole="status". - Color independence: Do not rely on color alone to convey meaning; provide numeric or text equivalents.
- Dark/Light mode: Implement theme switching following LIGHT_DARK_MODE_ACCESSIBILITY_BEST_PRACTICES:
- Default to system preference (
prefers-color-scheme) - Persist user choice in
localStorage - Validate contrast in both light and dark modes
- Default to system preference (
- No pass/fail certification: The UI must not imply that any single metric guarantees accessibility.
- Inclusive language: Use person-centered, respectful language throughout.
Please open an issue when reporting problems. We prioritize based on:
- Critical: A barrier that prevents users from completing core tasks (e.g., inaccessible color picker, broken keyboard navigation).
- High: Significant contrast failure or missing label in the tool itself.
- Medium: Guidance clarity issues, incomplete examples, or minor contrast gaps.
- Low: Minor improvements, typos, or enhancements.
CI workflows run on every pull request and on a monthly schedule:
- HTML validation – checks well-formed markup and valid attributes.
- Axe accessibility scan – runs
axe-coreagainst the live GitHub Pages deployment to flag WCAG violations. - Spell check – validates spelling in HTML and Markdown with
cspell.
See .github/workflows/ for the current workflow definitions.
This project targets the last 2 major versions of all major browser engines:
- Chrome/Chromium (including Edge, Brave, Opera)
- Firefox
- Safari/WebKit (macOS and iOS)
Contributors are encouraged to test with:
- Screen readers: JAWS, NVDA, VoiceOver, TalkBack
- Keyboard navigation: Tab, arrow keys, standard shortcuts
- Magnification tools: Browser zoom (up to 200%), screen magnifiers
- Voice control: Dragon, Voice Control
contrast-plus implements accessible theme switching following the LIGHT_DARK_MODE_ACCESSIBILITY_BEST_PRACTICES:
- A single toggle button (sun/moon icon) in the top-right corner of the header allows users to switch between light and dark themes.
- The toggle defaults to the user's OS/browser
prefers-color-schemepreference when no override exists. - User overrides are persisted across sessions via
localStorage. - The
aria-labelon the toggle button always describes the action (e.g., "Switch to dark mode" when currently in light mode). - CSS custom properties (
--color-text,--color-background, etc.) control all theme-sensitive colors. - Both light and dark themes are validated to meet WCAG 2.2 AA contrast requirements.
@media (forced-colors: active)styles preserve semantic boundaries in Windows High Contrast mode.
- contrast-plus is an educational tool, not a certification service. Outputs are informational only.
- The APCA Lc metric is loaded via a separate script; if that script fails to load, APCA values show
n/aas intentional graceful degradation. - Some visualization panels are inherently color-dependent (they demonstrate contrast). Numeric equivalents are always provided alongside visual previews.
- The application does not claim formal WCAG conformance; it aims to follow WCAG 2.2 AA patterns where feasible.
This project uses AI tools during development (see README.md for details). No AI is used at runtime — all contrast calculations are deterministic mathematical algorithms.
- Questions or bugs: Open an issue
- Accessibility feedback: Use the
accessibilitylabel when filing issues - Contributions: Follow the guidelines above; keep changes focused and document accessibility impact
We regularly review and update:
- WCAG conformance as standards evolve (currently targeting WCAG 2.2 AA)
- Contrast thresholds and APCA algorithm updates
- Keyboard and screen-reader behavior based on community feedback
- Inclusive language and terminology
Integrating accessibility checks into the CI/CD pipeline ensures regressions are caught before they reach users. This section documents the strategy used in contrast-plus and offers guidance for contributors adding new automation.
Full reference: CI/CD Accessibility Best Practices
Run audits locally before pushing. This is the fastest feedback loop and keeps CI noise low.
# Serve the site locally (matches GitHub Pages behavior)
npm run serve # python3 -m http.server 8005
# Then, in a second terminal:
npm run test:a11y # pa11y scan against http://localhost:8005/
npm run check # HTML validation + spell check| Workflow file | Trigger | What it checks |
|---|---|---|
quality.yml |
Push to main, every PR |
HTML validation, spell check, pa11y (WCAG 2 AA), link check, basic security |
axe-scan.yml |
Push to main, monthly |
axe-core scan of both pages |
a11y-scanner.yml |
Monthly, manual dispatch | GitHub AI accessibility scanner against the live GitHub Pages site |
All workflow definitions live in .github/workflows/.
- Critical failures (broken keyboard nav, inaccessible color picker) block merge via the
quality.ymlchecks. - Scheduled scan findings from
a11y-scanner.ymlare filed as GitHub Issues with theaccessibilitylabel. - If open accessibility issues exist, the monthly scanner is paused automatically to prevent alert fatigue.
- Issues are triaged using the severity taxonomy in §4 above.
When adding new interactive features or pages, update CI accordingly:
- Add the new URL to the
urlslist ina11y-scanner.ymland to the axe-core scan steps inaxe-scan.yml. - Test both light and dark themes — the
prefers-color-schememedia query affects contrast values. - Test on mobile viewport sizes — layout changes can introduce new accessibility issues.
- Export structured JSON reports when doing deep audits so findings are reviewable and traceable.
- Lighthouse CI — enforce 100% accessibility and performance scores as a quality gate.
- Playwright + axe-core — dynamic testing including open menus, modals, and theme emulation.
- AccessLint — inline PR comments without a full CI run.
- Open-Scans — external scans using multiple engines against a live URL.
- CivicActions: Scaling Automation — enterprise-scale a11y philosophy.
Because contrast-plus is a tool about color contrast, this project holds itself to a higher standard of contrast quality than a typical web application. This section summarizes the rules contributors must follow and the thresholds all UI colors must meet.
Full reference: Color Contrast Accessibility Best Practices
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 the applicable WCAG 2.2 Level AA contrast thresholds below. Contrast must be maintained in light mode, dark mode, and forced-colors (high contrast) mode.
| Success Criterion | Level | Requirement | Applies To |
|---|---|---|---|
| 1.4.1 Use of Color | A | Color must not be the only visual means of conveying information | All content |
| 1.4.3 Contrast (Minimum) | AA | 4.5:1 for normal text; 3:1 for large text | Text and images of text |
| 1.4.6 Contrast (Enhanced) | AAA | 7:1 for normal text; 4.5:1 for large text | Text and images of text |
| 1.4.11 Non-text Contrast | AA | 3:1 against adjacent colors | UI components, graphical objects |
| 2.4.13 Focus Appearance | AA | Focus indicator ≥ perimeter of component × 2 CSS px; 3:1 contrast change | Keyboard focus indicators |
| Text type | Minimum (AA) | Enhanced (AAA) |
|---|---|---|
| Normal text (below 18pt / 14pt bold) | 4.5:1 | 7:1 |
| Large text (18pt+ / 14pt+ bold) | 3:1 | 4.5:1 |
| Logotypes, incidental/decorative text, 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.
Form input borders, interactive component boundaries, meaningful icons, chart elements, and status indicators must all achieve 3:1 against their adjacent colors. Decorative graphics, inactive/disabled components, and logos are exempt.
Every keyboard-focusable element must have a visible focus indicator that:
- Encloses the component with a minimum area of the component's perimeter × 2 CSS pixels.
- Has 3:1 contrast between focused and unfocused states.
- Has 3:1 contrast against every adjacent color in the unfocused state.
Use outline for focus rings — it is preserved in forced-colors mode by default. Never suppress focus visibility with outline: none or outline: 0 without providing an equivalent replacement.
The CSS forced-colors media query replaces author colors with system-defined values. Ensure the UI:
- Uses
outline(notbox-shadow) for focus indicators. - Does not rely on
background-colorgradients or pseudo-element colors to convey information. - Applies
@media (forced-colors: active)overrides for SVG icons and custom components as needed.
Test using Chrome DevTools → Rendering → "Emulate CSS media feature forced-colors: active".
The Advanced Perceptual Contrast Algorithm (APCA) is the algorithm this tool measures and displays. APCA is not yet required by WCAG 2.2 but is expected in WCAG 3.0. Teams should continue to meet WCAG 2.2 AA requirements in parallel and treat APCA as supplemental guidance.
| 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 |
Lc values are signed (positive = dark-on-light, negative = light-on-dark); only the absolute value is compared against thresholds.
| Mistake | Why it fails | Fix |
|---|---|---|
outline: none with no replacement |
Focus invisible for keyboard users (2.4.7, 2.4.13) | Provide a visible custom focus style |
| Low-contrast placeholder text | Placeholder below 4.5:1 fails 1.4.3 | Use a placeholder color ≥ 4.5:1 or place labels outside the field |
| Error states shown only with red color | Color is sole cue (1.4.1) | Add error icon, text label, and aria-invalid |
| Contrast checked only in light mode | Dark mode or high contrast mode may fail | Test all modes |
| Gradient background behind text | Contrast varies across the gradient | Verify at the lowest-contrast region or use a solid overlay |
| Icon-only buttons with low-contrast icons | Icon fails 3:1 non-text requirement (1.4.11) | Render icons in a color with ≥ 3:1 contrast against its background |
- Run axe-core
color-contrastrule against all pages (npm run test:a11y) - Run
color-contrast-enhancedrule for AAA coverage - Validate focus indicator contrast via axe
focus-order-semanticsandfocus-visiblerules
- Check text contrast for all text sizes with a contrast checker tool
- Check non-text contrast for all form controls, icons, and data visualizations
- Verify focus ring is visible on all interactive elements in default and dark modes
- Test in Windows High Contrast / forced-colors mode
- Test at browser zoom 200% and 400%
- Review all interactive states: default, hover, focus, active, visited, error, disabled
- View the page in grayscale and confirm all information conveyed by color is also conveyed by text, icons, or patterns
Last updated: 2026-03-31