DEV Community

Cover image for Building an Advanced Color Contrast Checker for Accessibility (WCAG Guide + Auto-Fix)
Joe Bou Khalil
Joe Bou Khalil

Posted on

Building an Advanced Color Contrast Checker for Accessibility (WCAG Guide + Auto-Fix)

There are a lot of colors, but not all colors go together.

How to choose between colors? Which colors go with each? And even how color-blind people see colors?

All of these problems are solved in this code.

What it does?

  • You choose 2 colors you want.

  • It tells you if they go together.

  • You can make the app choose a color that matches.

  • And even you can choose between different color-blind disorders. And see how the colors are seen.

Code

`<!DOCTYPE html>

Advanced Contrast Checker body { font-family: Arial; background: #0f172a; color: white; text-align: center; padding: 20px; } .container { max-width: 600px; margin: auto; background: #1e293b; padding: 20px; border-radius: 15px; } input, select { margin: 10px; padding: 10px; border-radius: 10px; } .preview { margin-top: 20px; padding: 40px; border-radius: 10px; font-size: 22px; } button { padding: 10px; border-radius: 10px; margin: 5px; cursor: pointer; } .good {color: #22c55e;} .medium {color: #facc15;} .bad {color: #ef4444;}

🎨 Advanced Contrast Checker

<br>





    Normal Text

    Large Text





Check

Auto Fix



Accessible Text



<p id="result"></p>

<p id="details"></p>



<h3>Color Blind Simulation</h3>



    None

    Protanopia

    Deuteranopia

    Tritanopia
Enter fullscreen mode Exit fullscreen mode

function hexToRgb(hex){

let bigint=parseInt(hex.slice(1),16);

return {r:(bigint&gt;&gt;16)&amp;255,g:(bigint&gt;&gt;8)&amp;255,b:bigint&amp;255};
Enter fullscreen mode Exit fullscreen mode

}

function luminance(r,g,b){

let a=[r,g,b].map(v=&gt;{

    v/=255;

    return v&lt;=0.03928?v/12.92:Math.pow((v+0.055)/1.055,2.4);

});

return 0.2126*a[0]+0.7152*a[1]+0.0722*a[2];
Enter fullscreen mode Exit fullscreen mode

}

function contrast(c1,c2){

let L1=luminance(c1.r,c1.g,c1.b);

let L2=luminance(c2.r,c2.g,c2.b);

return (Math.max(L1,L2)+0.05)/(Math.min(L1,L2)+0.05);
Enter fullscreen mode Exit fullscreen mode

}

function check(){

let bg=document.getElementById("bg").value;

let text=document.getElementById("text").value;



let c1=hexToRgb(bg);

let c2=hexToRgb(text);



let ratio=contrast(c1,c2).toFixed(2);



let mode=document.getElementById("mode").value;

let pass = mode==="large" ? 3 : 4.5;



let result="";

let cls="";



if(ratio&gt;=7){ result="AAA"; cls="good";}

else if(ratio&gt;=pass){ result="AA"; cls="medium";}

else { result="Poor"; cls="bad"; }



document.getElementById("preview").style.background=bg;

document.getElementById("preview").style.color=text;



document.getElementById("result").innerHTML =

    `Contrast: ${ratio} - &lt;span class="${cls}"&gt;${result}&lt;/span&gt;`;



document.getElementById("details").innerHTML =

    `BG: ${bg} | TEXT: ${text}`;



speak(result);
Enter fullscreen mode Exit fullscreen mode

}

function autoFix(){

let bg=document.getElementById("bg").value;

let text="#ffffff";



let c1=hexToRgb(bg);



for(let i=0;i&lt;=255;i++){

    let test=`rgb(${i},${i},${i})`;

    let c2={r:i,g:i,b:i};

    if(contrast(c1,c2)&gt;=4.5){

        document.getElementById("text").value=rgbToHex(i,i,i);

        break;

    }

}

check();
Enter fullscreen mode Exit fullscreen mode

}

function rgbToHex(r,g,b){

return "#" + r,g,b].map(x=&gt;x.toString(16).padStart(2,"0")).join("");
Enter fullscreen mode Exit fullscreen mode

}

function speak(text){

let msg=new SpeechSynthesisUtterance("Contrast is " + text);

speechSynthesis.speak(msg);
Enter fullscreen mode Exit fullscreen mode

}

function simulate(type){

let preview=document.getElementById("preview");



if(type==="protanopia"){

    preview.style.filter="grayscale(50%)";

} else if(type==="deuteranopia"){

    preview.style.filter="sepia(60%)";

} else if(type==="tritanopia"){

    preview.style.filter="invert(20%)";

} else {

    preview.style.filter="none";

}
Enter fullscreen mode Exit fullscreen mode

}

check();

`

What does the code do?

Color Conversion (Hex → RGB)

`function hexToRgb(hex){

let bigint = parseInt(hex.slice(1),16);

return {

    r:(bigint>>16)&255,

    g:(bigint>>8)&255,

    b:bigint&255

};
Enter fullscreen mode Exit fullscreen mode

}`

  • Converts colors like #ffffff into numbers:

  • Red, green, and blue values (0–255)

Luminance Calculation (VERY IMPORTANT)

`function luminance(r,g,b){

let a=[r,g,b].map(v=>{

    v/=255;

    return v<=0.03928 ? v/12.92 : Math.pow((v+0.055)/1.055,2.4);

});

return 0.2126*a[0]+0.7152*a[1]+0.0722*a[2];
Enter fullscreen mode Exit fullscreen mode

}`

  • Measures how bright a color actually is

  • Not just visually—mathematically accurate (WCAG standard)

Example:

  • White → high luminance

  • Black → low luminance

Contrast Ratio Formula (THE CORE)

`function contrast(c1,c2){

let L1=luminance(c1.r,c1.g,c1.b);

let L2=luminance(c2.r,c2.g,c2.b);

return (Math.max(L1,L2)+0.05)/(Math.min(L1,L2)+0.05);
Enter fullscreen mode Exit fullscreen mode

}`

  • Calculates the contrast ratio between two colors

  • This determines accessibility.

Output examples:

  • 21 → perfect contrast

  • 4.5+ → good

  • <4.5 → bad

And here's even a demo if you want to try it yourself.

Who can use it?

  • People who work in design.

  • People who want to see colors and are colorblind.

  • People in web development.

Conclusion

This project has been built on the view of helping people. And everything can be built with some work and mindset.

Top comments (0)