Skip to main content Accessibility Feedback

Dynamically changing the text color based on background color contrast with vanilla JS

Last month, we looked at a technique for generating random colors with vanilla JS.

Reader Stephen Flannery showed me a demo he built with the technique where the text color changed from black to white if the color was too dark.

Checking color contrast with vanillia JS

When I asked Stephen how he did it, he pointed me to this article from Brian Suda at 24 Ways on checking color contrast. In it, Brian shares this technique.

The second equation is called ‘YIQ’ because it converts the RGB color space into YIQ, which takes into account the different impacts of its constituent parts…

You’ll notice first that we have broken down the hex value into separate RGB values. This is important because each of these channels is scaled in accordance to its visual impact. Once everything is scaled and normalized, it will be in a range between zero and 255. Much like the previous ’50%’ function, we now need to check if the input is above or below halfway. Depending on where that value is, we’ll return the corresponding highest contrasting color.

And here’s a helper function he wrote to go with the technique.

function getContrastYIQ(hexcolor){
	var r = parseInt(hexcolor.substr(0,2),16);
	var g = parseInt(hexcolor.substr(2,2),16);
	var b = parseInt(hexcolor.substr(4,2),16);
	var yiq = ((r*299)+(g*587)+(b*114))/1000;
	return (yiq >= 128) ? 'black' : 'white';
}

The helper function is really neat. Let’s build on it.

Adding flexibility to our contrast checker function

Brian’s function only accepts six-character hexcolors, and they cannot have a leading hash (#).

Let’s first modify it to accept a leading hash. We’ll use Array.slice() to get the first character and check if it equals #. If it does, we’ll use Array.slice() again to remove the leading hash and redefine hexcolor.

var getContrast = function (hexcolor){

	// If a leading # is provided, remove it
	if (hexcolor.slice(0, 1) === '#') {
		hexcolor = hexcolor.slice(1);
	}

	// Convert to RGB value
	var r = parseInt(hexcolor.substr(0,2),16);
	var g = parseInt(hexcolor.substr(2,2),16);
	var b = parseInt(hexcolor.substr(4,2),16);

	// Get YIQ ratio
	var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;

	// Check contrast
	return (yiq >= 128) ? 'black' : 'white';

};

Next, let’s modify the function to allow both three and six-character colors.

To do that, we’ll first check the length of hexcolor. If it’s 3, we’ll use Array.split() to convert the hexcolor string into an array of characters. Then, we’ll use Array.map() to double each character, and Array.join() to combine it back into a string.

/*!
 * Get the contrasting color for any hex color
 * (c) 2019 Chris Ferdinandi, MIT License, https://gomakethings.com
 * Derived from work by Brian Suda, https://24ways.org/2010/calculating-color-contrast/
 * @param  {String} A hexcolor value
 * @return {String} The contrasting color (black or white)
 */
var getContrast = function (hexcolor){

	// If a leading # is provided, remove it
	if (hexcolor.slice(0, 1) === '#') {
		hexcolor = hexcolor.slice(1);
	}

	// If a three-character hexcode, make six-character
	if (hexcolor.length === 3) {
		hexcolor = hexcolor.split('').map(function (hex) {
			return hex + hex;
		}).join('');
	}

	// Convert to RGB value
	var r = parseInt(hexcolor.substr(0,2),16);
	var g = parseInt(hexcolor.substr(2,2),16);
	var b = parseInt(hexcolor.substr(4,2),16);

	// Get YIQ ratio
	var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;

	// Check contrast
	return (yiq >= 128) ? 'black' : 'white';

};

Bringing it all together

Here’s a demo on CodePen. Reload the page to get a new color. You can download the modified helper function on the Vanilla JS Toolkit.

This technique works in all modern browsers, and IE9 and above.