Some websites and services allow you to customize your profile by uploading pictures, changing the background color or other aspects of the design. As a customer, this personalization turns a web app into your little nest where you store your data. As a designer, letting your customers have free rein over the layout and design is a scary prospect. So what happens to all the stock text and images that are designed to work on nice white backgrounds? Even the Mac only lets you choose between two colors for the OS, blue or graphite! Opening up the ability to customize your site’s color scheme can be a recipe for disaster unless you are flexible and understand how to find maximum color contrasts.
In this article I will walk you through two simple equations to determine if you should be using white or black text depending on the color of the background. The equations are both easy to implement and produce similar results. It isn’t a matter of which is better, but more the fact that you are using one at all! That way, even with the craziest of Geocities color schemes that your customers choose, at least your text will still be readable.
Let’s have a look at a range of various possible colors. Maybe these are pre-made color schemes, corporate colors, or plucked from an image.

Now that we have these potential background colors and their hex values, we need to find out whether the corresponding text should be in white or black, based on which has a higher contrast, therefore affording the best readability. This can be done at runtime with JavaScript or in the back-end before the HTML is served up.
There are two functions I want to compare. The first, I call ’50%’. It takes the hex value and compares it to the value halfway between pure black and pure white. If the hex value is less than half, meaning it is on the darker side of the spectrum, it returns white as the text color. If the result is greater than half, it’s on the lighter side of the spectrum and returns black as the text value.
In PHP:
function getContrast50($hexcolor){
return (hexdec($hexcolor) > 0xffffff/2) ? 'black':'white';
}
In JavaScript:
function getContrast50(hexcolor){
return (parseInt(hexcolor, 16) > 0xffffff/2) ? 'black':'white';
}
It doesn’t get much simpler than that! The function converts the six-character hex color into an integer and compares that to one half the integer value of pure white. The function is easy to remember, but is naive when it comes to understanding how we perceive parts of the spectrum. Different wavelengths have greater or lesser impact on the contrast.
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. Again, the equation returns white or black and it’s also very easy to implement.
In PHP:
function getContrastYIQ($hexcolor){
$r = hexdec(substr($hexcolor,0,2));
$g = hexdec(substr($hexcolor,2,2));
$b = hexdec(substr($hexcolor,4,2));
$yiq = (($r*299)+($g*587)+($b*114))/1000;
return ($yiq >= 128) ? 'black' : 'white';
}
In JavaScript:
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';
}
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.
That’s it: two simple contrast equations which work really well to determine the best readability.
If you are interested in learning more, the W3C has a few documents about color contrast and how to determine if there is enough contrast between any two colors. This is important for accessibility to make sure there is enough contrast between your text and link colors and the background.
There is also a great article by Kevin Hale on Particletree about his experience with choosing light or dark themes. To round it out, Jonathan Snook created a color contrast picker which allows you to play with RGB sliders to get values for YIQ, contrast and others. That way you can quickly fiddle with the knobs to find the right balance.
Comparing results
Let’s revisit our color schemes and see which text color is recommended for maximum contrast based on these two equations.

If we use the simple ’50%’ contrast function, we can see that it recommends black against all the colors except the dark green and purple on the second row. In general, the equation feels the colors are light and that black is a better choice for the text.

The more complex ‘YIQ’ function, with its weighted colors, has slightly different suggestions. White text is still recommended for the very dark colors, but there are some surprises. The red and pink values show white text rather than black. This equation takes into account the weight of the red value and determines that the hue is dark enough for white text to show the most contrast.
As you can see, the two contrast algorithms agree most of the time. There are some instances where they conflict, but overall you can use the equation that you prefer. I don’t think it is a major issue if some edge-case colors get one contrast over another, they are still very readable.
Now let’s look at some common colors and then see how the two functions compare. You can quickly see that they do pretty well across the whole spectrum.

In the first few shades of grey, the white and black contrasts make sense, but as we test other colors in the spectrum, we do get some unexpected deviation. Pure red #FF0000 has a flip-flop. This is due to how the ‘YIQ’ function weights the RGB parts. While you might have a personal preference for one style over another, both are justifiable.

In this second round of colors, we go deeper into the spectrum, off the beaten track. Again, most of the time the contrasting algorithms are in sync, but every once in a while they disagree. You can select which you prefer, neither of which is unreadable.
Conclusion
Contrast in color is important, especially if you cede all control and take a hands-off approach to the design. It is important to select smart defaults by making the contrast between colors as high as possible. This makes it easier for your customers to read, increases accessibility and is generally just easier on the eyes.
Sure, there are plenty of other equations out there to determine contrast; what is most important is that you pick one and implement it into your system.
So, go ahead and experiment with color in your design. You now know how easy it is to guarantee that your text will be the most readable in any circumstance.


Comments
Comments are ordered by helpfulness, as indicated by you. Help us pick out the gems and discourage asshattery by voting on notable comments.
Got something to add? You can leave a comment below.
24/12/2010
This is awesome! Thanks Brian. I’ll definitely be playing with the PHP/Javascript in order to better select when to use black or white text.
Vote Helpful or Unhelpful
24/12/2010
useful article, thank you Brian
Vote Helpful or Unhelpful
24/12/2010
The second method looks very useful, but I have reservations about the 50% method. Because the red portion of the color is the most significant two digits when you treat it as a number, that is effectively the only component that has any impact on the decision. It’s equivalent to using the other method but weighting red by a factor of 256 relative to green and 65,000 relative to blue.
Vote Helpful or Unhelpful
24/12/2010
Don’t be silly, this doesn’t work:
function getContrast50(hexcolor){ return (parseInt(hexcolor, 16) > 0xffffff/2) ? ‘black’:‘white’; }That’ll say a background color of #010000 should have black text, and a background color of #7FFFFF should have
white text. That’s ludicrous!
Vote Helpful or Unhelpful
24/12/2010
Eric – have you tried it?
Example: http://drewmclellan.net/code/contrast/
Vote Helpful or Unhelpful
24/12/2010
Ok, it looks like I made a mistake reading that. #010000 does give the sensible choice of white.
However, I still feel that treating the entire color as one number is inherently wrong. Provided I keep my red component below 0×80, the text color will ALWAYS be white. That seems like a bad choice.
What I assume the author meant to do was to take the average of the components, and see if they were more than 50%. So:
return (r + g + b) / 3 > 0.5 ? ‘black’ : ‘white’;
Vote Helpful or Unhelpful
24/12/2010
Eric – that sounds like a sensible way to improve that algorithm. That doesn’t make the original ludicrous, as you put it. It’s basic and has room for improvement – the article already explores that.
Vote Helpful or Unhelpful
24/12/2010
This is brilliant.
As a reminder to all (though I doubt 24ways readers need this reminder), always explicitly define your background color and don’t assume everyone uses white as a default. I can’t tell you the number of Company’s that I’ve seen this from. Sites like ebay, chase, Google, etc. are usually good about their home pages and certain other pages, but there are many other pages where this is forgotten about. Techniques like this in the article would fall apart if the user didn’t have the white background you assumed they’d just have.
Vote Helpful or Unhelpful
24/12/2010
It’s really a great and useful article. Thank you Brian.
Vote Helpful or Unhelpful
25/12/2010
Great article Brian!
I needed the knowledge from this article few years ago as there was a lot to explore and learn about colours before wrapping up the yiq technique.
@Drew thanks for the demo page
Vote Helpful or Unhelpful
26/12/2010
As someone who’s primarily a programmer, and not really a designer, I, like Eric, at first considered the first algorithm revolting. It is indeed flawed, and figuring out which colors would be pathological to the algorithm is not a particularly challenging endeavor.
However, I realized it will probably work alright, and in case there are more readers like myself and Eric, I will provide an explanation that at least I find more satisfying than the one given in the article:
Most video formats are still encoded in black and white, with added color channels. While this was originally done for backwards compatibility reasons, this also turns out to compress really well, since it turns out that the red, green and blue channels are very often quite similar: by separating the channels in a YIQ or a YUV picture, the luma channel will likely be the only one with a lot of information. By separating the channels in a RGB picture, all of the channels will appear to contain pretty much the same shapes. This must mean that most things are “somewhat greyish”: the channels in RGB are rarely that far apart.
Exploiting this, as the 50% method appears to do, by assuming that the green and blue channel will be similar to the red will thus often give you somewhat sane results.
This assumption does break sometimes, particularly for highly saturated colors such as #ff0000, but for those, either black or white text works.
Sure, #7fffff and #800000 will give you really bad results, but other than those, it should work alright.
Vote Helpful or Unhelpful
27/12/2010
Great article Brian. Too bad this was the last post of 2010.
Thanks to all for the great tips this year.
Vote Helpful or Unhelpful
28/12/2010
Definitely an interesting idea! I like the results the YIQ algorithm provides.
Also, this is a bit of a nitpick, but at least according to Wikipedia, the blue value should be multiplied by 114, not 144.
Vote Helpful or Unhelpful
28/12/2010
Clever and simple way to resolve a common color problem, Thanks !
Vote Helpful or Unhelpful
28/12/2010
Hy
I found the article very interesting and so I create spontaneously a C# function. It may be any value between
#000000 and #ffffff calculate.
Testing: http://stastka.ch/tools/color/
Vote Helpful or Unhelpful
28/12/2010
Very much liked this article. I posted some jQuery code for using it to style tag links: http://dco1.tumblr.com/post/2501743028/24-ways-calculating-color-contrast
Vote Helpful or Unhelpful
29/12/2010
J. RYAN STINNETT, you are right. It seems to be a typo that made it pretty far. I have updated the text so the function is 114 not 144. Thanks for that catch, with enough eyeballs all bugs are shallow.
Vote Helpful or Unhelpful
29/12/2010
Brian,
What is your opinion of using luminance for this computation?
let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
if luminance < 110 light, else dark (might be backwards)
This is how Firefox determines whether a given textcolor in a Persona should have a light or dark text shadow.
Vote Helpful or Unhelpful
01/01/2011
This could come in handy sometime, nice article.
I do however disagree with the ‘…neither of which is unreadable.’ remark regarding choosing between the 50% or YIQ method.
As Drew’s test page clearly shows, white text on—for instance—#00FFFF hurts your eyes and isn’t readable at all. The YIQ method seems like the way to go in my opinion, it produces far more readable results in all the tests appearing in this article.
Vote Helpful or Unhelpful
01/01/2011
Mike Kaply, there are a few equations for contrast with luminance, this is the W3C definition, which is very similar
http://www.w3.org/TR/2006/WD-WCAG20-20060427/appendixA.html#luminosity-contrastdef
They also have a more complex equation which I wanted to avoid in this quick article.
http://www.w3.org/WAI/GL/WCAG20/WD-WCAG20-TECHS/G145.html
Testing the contrast by luminance is yet another tool to have in your tool box. I don’t think it really matters which equation you choose, there will always be issues with edge-cases, it is more important that you are using something.
Vote Helpful or Unhelpful
02/01/2011
The 50% algorithm is incorrect and, as other commentors note, basically only considers red.
The test colors are flawed, too; they are all completely saturated (or exactly grey). This means that even an incredibly naïve algorithm like the 50% one will always have contrast simply because both white and black always contrast against saturated colors — and because when r = g = b (grey) then it doesn’t matter if red is over-represented.
I really feel the article needs updated to reflect this. It’s that big a deal, and I think 24 Ways commands enough respect that readers expect/deserve a correction.
Vote Helpful or Unhelpful
15/01/2011
I’ve just read this technique by Brian (have a bit of catching up to do on the old RSS feeds) and decided to implement it in Sass/Compass:
https://gist.github.com/781017
The end result:
http://cl.ly/42Kz
For a quick 5 minute hack, this may come in very handy indeed. Thanks.
Vote Helpful or Unhelpful
15/01/2011
Hey Brian, thanks for sharing this.. just what i wanted, and wupti.. i got it :)
Vote Helpful or Unhelpful
Impress us