Build a Color Contrast Checker Chrome Extension: Complete Guide
Web accessibility is no longer optional—it is a legal requirement and a moral imperative. One of the most critical aspects of accessibility is ensuring that text has sufficient color contrast against its background. This is where a contrast checker extension becomes an invaluable tool for developers, designers, and accessibility auditors alike. In this comprehensive guide, we will walk you through the process of building a fully functional color contrast checker Chrome extension from scratch.
This tutorial is perfect for developers who want to expand their Chrome extension portfolio while contributing to a more accessible web. By the end of this guide, you will have a working extension that calculates WCAG contrast ratios, provides real-time feedback, and helps ensure accessibility color standards are met on any webpage.
Understanding Color Contrast and WCAG Standards
Before diving into code, it is essential to understand why color contrast matters and what the WCAG (Web Content Accessibility Guidelines) specify. The WCAG provides two levels of conformance: AA (minimum) and AAA (enhanced). For most websites, meeting WCAG AA standards is the goal, which requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text.
A contrast checker extension must accurately calculate these ratios based on the relative luminance of the foreground and background colors. The mathematical formula for relative luminance involves converting sRGB values to linear RGB values and applying specific coefficients. While this may sound complex, we will implement the calculation in JavaScript, making it straightforward to integrate into your extension.
The WCAG contrast requirements exist to ensure that people with visual impairments, including those with color blindness or low vision, can read text content on websites. According to the World Health Organization, approximately 430 million people globally have some form of visual impairment. By building a contrast checker extension, you are helping developers create more inclusive web experiences.
Project Setup and Extension Structure
Every Chrome extension begins with a manifest file and a set of JavaScript, HTML, and CSS files. For our color contrast checker extension, we will create a popup-based extension that allows users to select colors and see instant contrast calculations.
Create a new folder for your project and set up the following file structure:
contrast-checker/
├── manifest.json
├── popup.html
├── popup.js
├── popup.css
└── icons/
├── icon16.png
├── icon48.png
└── icon128.png
The manifest.json file is the backbone of your extension. It defines the extension’s name, version, permissions, and the files that Chrome will load. For our contrast checker, we need minimal permissions since we will primarily be calculating colors chosen by the user rather than analyzing webpage content directly.
Here is the manifest.json for our extension:
{
"manifest_version": 3,
"name": "Color Contrast Checker",
"version": "1.0",
"description": "Check color contrast ratios for WCAG accessibility compliance",
"permissions": ["activeTab"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
This manifest uses Manifest V3, which is the current standard for Chrome extensions. The “action” key defines the popup that appears when users click the extension icon in their browser toolbar.
Building the Popup Interface
The popup.html file defines the user interface for your contrast checker extension. We need inputs for selecting foreground and background colors, displays for the contrast ratio, and visual indicators showing whether the combination passes WCAG standards.
Create popup.html with the following structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Color Contrast Checker</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<h1>Contrast Checker</h1>
<div class="color-inputs">
<div class="color-group">
<label for="foreground">Foreground</label>
<input type="color" id="foreground" value="#000000">
<input type="text" id="foreground-text" value="#000000" maxlength="7">
</div>
<div class="color-group">
<label for="background">Background</label>
<input type="color" id="background" value="#ffffff">
<input type="text" id="background-text" value="#ffffff" maxlength="7">
</div>
</div>
<div class="preview" id="preview">
<p>Sample Text</p>
</div>
<div class="results">
<div class="ratio-display">
<span class="ratio-label">Contrast Ratio:</span>
<span class="ratio-value" id="ratio-value">21.00</span>
</div>
<div class="wcag-results">
<div class="wcag-level" id="aa-normal">
<span class="status fail">Fail</span>
<span>AA Normal</span>
</div>
<div class="wcag-level" id="aa-large">
<span class="status pass">Pass</span>
<span>AA Large</span>
</div>
<div class="wcag-level" id="aaa-normal">
<span class="status fail">Fail</span>
<span>AAA Normal</span>
</div>
<div class="wcag-level" id="aaa-large">
<span class="status pass">Pass</span>
<span>AAA Large</span>
</div>
</div>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
The interface includes color pickers, text inputs for manual hex code entry, a preview area showing how the colors look together, and a results section displaying the contrast ratio and WCAG compliance levels. This comprehensive layout makes the extension useful for both quick checks and detailed accessibility auditing.
Styling the Extension
The popup.css file styles the extension to make it visually appealing and user-friendly. A well-designed extension encourages regular use and makes the contrast checking process more enjoyable.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
width: 320px;
padding: 16px;
background: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 18px;
margin-bottom: 16px;
color: #333;
}
.color-inputs {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.color-group {
flex: 1;
}
.color-group label {
display: block;
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.color-group input[type="color"] {
width: 100%;
height: 40px;
border: none;
cursor: pointer;
}
.color-group input[type="text"] {
width: 100%;
padding: 6px;
margin-top: 4px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
}
.preview {
padding: 24px;
border-radius: 4px;
margin-bottom: 16px;
text-align: center;
border: 1px solid #ddd;
}
.preview p {
font-size: 18px;
font-weight: 500;
}
.results {
border-top: 1px solid #eee;
padding-top: 16px;
}
.ratio-display {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.ratio-label {
font-size: 14px;
color: #666;
}
.ratio-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.wcag-results {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.wcag-level {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
}
.status {
padding: 2px 8px;
border-radius: 4px;
font-weight: 600;
font-size: 10px;
}
.status.pass {
background: #d4edda;
color: #155724;
}
.status.fail {
background: #f8d7da;
color: #721c24;
}
This CSS creates a clean, modern interface with clear visual feedback for pass/fail states. The green and red color coding makes it immediately obvious whether a color combination meets accessibility standards.
Implementing Contrast Calculations
The popup.js file contains the core logic for calculating contrast ratios and determining WCAG compliance. This is where the mathematical formulas for relative luminance and contrast ratio come into play.
document.addEventListener('DOMContentLoaded', () => {
const foregroundInput = document.getElementById('foreground');
const backgroundInput = document.getElementById('background');
const foregroundText = document.getElementById('foreground-text');
const backgroundText = document.getElementById('background-text');
const preview = document.getElementById('preview');
const ratioValue = document.getElementById('ratio-value');
const aaNormal = document.getElementById('aa-normal').querySelector('.status');
const aaLarge = document.getElementById('aa-large').querySelector('.status');
const aaaNormal = document.getElementById('aaa-normal').querySelector('.status');
const aaaLarge = document.getElementById('aaa-large').querySelector('.status');
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function rgbToHex(r, g, b) {
return '#' + [r, g, b].map(x => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}).join('');
}
function getRelativeLuminance(r, g, b) {
const [rs, gs, bs] = [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 * rs + 0.7152 * gs + 0.0722 * bs;
}
function getContrastRatio(luminance1, luminance2) {
const lighter = Math.max(luminance1, luminance2);
const darker = Math.min(luminance1, luminance2);
return (lighter + 0.05) / (darker + 0.05);
}
function updateStatus(element, passes) {
element.textContent = passes ? 'Pass' : 'Fail';
element.className = 'status ' + (passes ? 'pass' : 'fail');
}
function calculateContrast() {
const fgHex = foregroundText.value;
const bgHex = backgroundText.value;
const fg = hexToRgb(fgHex);
const bg = hexToRgb(bgHex);
if (!fg || !bg) return;
const fgLuminance = getRelativeLuminance(fg.r, fg.g, fg.b);
const bgLuminance = getRelativeLuminance(bg.r, bg.g, bg.b);
const ratio = getContrastRatio(fgLuminance, bgLuminance);
const ratioRounded = Math.round(ratio * 100) / 100;
ratioValue.textContent = ratioRounded;
preview.style.color = fgHex;
preview.style.backgroundColor = bgHex;
// WCAG AA requires 4.5:1 for normal text, 3:1 for large text
// WCAG AAA requires 7:1 for normal text, 4.5:1 for large text
updateStatus(aaNormal, ratio >= 4.5);
updateStatus(aaLarge, ratio >= 3);
updateStatus(aaaNormal, ratio >= 7);
updateStatus(aaaLarge, ratio >= 4.5);
}
foregroundInput.addEventListener('input', (e) => {
foregroundText.value = e.target.value;
calculateContrast();
});
backgroundInput.addEventListener('input', (e) => {
backgroundText.value = e.target.value;
calculateContrast();
});
foregroundText.addEventListener('input', () => {
const val = foregroundText.value;
if (/^#[0-9A-Fa-f]{6}$/.test(val)) {
foregroundInput.value = val;
calculateContrast();
}
});
backgroundText.addEventListener('input', () => {
const val = backgroundText.value;
if (/^#[0-9A-Fa-f]{6}$/.test(val)) {
backgroundInput.value = val;
calculateContrast();
}
});
calculateContrast();
});
This JavaScript implements the WCAG algorithm for calculating contrast ratios. The getRelativeLuminance function converts sRGB values to linear RGB values using the WCAG formula, while getContrastRatio applies the contrast ratio calculation. The updateStatus function then determines whether each WCAG level passes or fails based on the calculated ratio.
Advanced Features for a Production Extension
While the basic contrast checker works well, you can enhance it with additional features to make it more useful for developers. Consider adding the ability to analyze colors directly from the active webpage, saving color combinations for future reference, or providing suggestions for achieving compliant color pairs.
For webpage analysis, you would need to inject a content script that can detect the colors used in elements when the user clicks on them. This requires additional permissions and a more complex implementation, but it makes the extension significantly more powerful for real-world accessibility auditing.
Another valuable feature is automatic color suggestions. If a color combination fails WCAG requirements, the extension could suggest modified colors that would pass while staying close to the user’s original choice. This makes the tool more practical for designers who need to find compliant alternatives.
Loading and Testing Your Extension
Before publishing, you need to test your extension locally. Chrome provides a simple way to load unpacked extensions for testing. Open chrome://extensions in your browser, enable “Developer mode” in the top right corner, and click “Load unpacked.” Select your extension’s folder, and the extension will appear in your toolbar.
Test the extension by selecting different color combinations and verifying that the contrast ratios and WCAG compliance indicators update correctly. Pay special attention to edge cases, such as very light colors against very dark colors, to ensure your calculations are accurate.
You should also test the extension across different websites to ensure the popup displays correctly and doesn’t conflict with page styles. The extension should work consistently regardless of the website it is activated on.
Publishing to the Chrome Web Store
Once your extension is tested and working correctly, you can publish it to the Chrome Web Store. First, you need to create a developer account if you don’t have one. The registration process requires a one-time fee of $5.
To prepare for publication, create a zip file containing all your extension files. Log in to the Chrome Web Store Developer Dashboard, click “New Item,” and upload your zip file. Fill in the extension details, including a compelling description that highlights the accessibility color features and keywords like “contrast checker extension” and “WCAG compliance.”
Your description should emphasize how the extension helps developers ensure their websites meet accessibility standards. Use the keywords naturally throughout the description to improve search visibility for users searching for “wcag contrast chrome” and “accessibility color” tools.
After submitting, Google will review your extension. The review process typically takes a few hours to a few days. Once approved, your extension will be available in the Chrome Web Store for millions of users to install and use.
Conclusion
Building a color contrast checker extension is an excellent project that combines practical utility with meaningful impact. The accessibility color tools market continues to grow as more organizations recognize the importance of inclusive web design. By creating a contrast checker extension, you are not only adding a valuable tool to your development portfolio but also contributing to a more accessible internet.
The skills you learn from this project—working with Chrome extension APIs, implementing WCAG algorithms, and building polished user interfaces—are transferable to many other extension ideas. Whether you continue to improve this contrast checker or move on to new projects, you now have a solid foundation in Chrome extension development.
Remember to regularly update your extension based on user feedback, fix any bugs promptly, and consider adding new features that make accessibility checking even easier. With dedication and attention to user needs, your contrast checker extension can become a trusted tool for developers worldwide who care about creating accessible web experiences.