Users can set the background-color of a button through a textbox that accept RGB hexadecimal notation: ff00ff
, ccaa22
, etc. So I need to set the text color to the opposite. Not sure about the terminology (opposite color) but the idea is assure readability.

- 32,158
- 14
- 82
- 96

- 10,214
- 10
- 69
- 101
-
This will help you: http://stackoverflow.com/questions/1664140/js-function-to-calculate-complementary-colour – Smamatti Mar 07 '12 at 11:14
-
1what needs to be done is convert this RGB to HSL, complement the Hue(hue=hue+180mod360), and convert it back to RGB... look for the conversion codes http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript – Tejasva Dhyani Mar 07 '12 at 11:53
-
@ smamatti && tejasva-dhyani: I am studying both solutions. Seems very promising. Thanks! – Igor Parra Mar 07 '12 at 12:08
-
Actually that comes out to be 15's complement of the hex value...I don't know if it will work or not. – Tejasva Dhyani Mar 07 '12 at 12:13
10 Answers
You can invert the background color and use it as foreground color. The following algorithm produces results identical to the "Image > Adjustments > Invert" color command in Photoshop:
function invertColor(hexTripletColor) {
var color = hexTripletColor;
color = color.substring(1); // remove #
color = parseInt(color, 16); // convert to integer
color = 0xFFFFFF ^ color; // invert three bytes
color = color.toString(16); // convert to hex
color = ("000000" + color).slice(-6); // pad with leading zeros
color = "#" + color; // prepend #
return color;
}
/*
* Demonstration
*/
function randomColor() {
var color;
color = Math.floor(Math.random() * 0x1000000); // integer between 0x0 and 0xFFFFFF
color = color.toString(16); // convert to hex
color = ("000000" + color).slice(-6); // pad with leading zeros
color = "#" + color; // prepend #
return color;
}
$(function() {
$(".demolist li").each(function() {
var c1 = randomColor();
var c2 = invertColor(c1);
$(this).text(c1 + " " + c2).css({
"color": c1,
"background-color": c2
});
});
});
body { font: bold medium monospace; }
.demolist { margin: 0; padding: 0; list-style-type: none; overflow: hidden; }
.demolist li { float: left; width: 5em; height: 5em; text-align: center; }
<ul class="demolist">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
Note that this is not a bullet-proof solution. Colors that are close to 50% brightness and/or saturation will not produce sufficient contrast.

- 262,204
- 82
- 430
- 521
-
-
1Works really well for most colors but - as the note above suggests - not for all. For grey (#7F7F7F) it returns (#808080), which is practically identical. "Close but no cigar" :( – Zeek2 Feb 06 '17 at 15:22
Salaman's code is good but sometimes his inversion is not readable enough. I use YCbCr and just change gray scale.
function invertColor(rgb) {
var yuv = rgb2yuv(rgb);
var factor = 180;
var threshold = 100;
yuv.y = clamp(yuv.y + (yuv.y > threshold ? -factor : factor));
return yuv2rgb(yuv);
}

- 1,420
- 9
- 17
-
1this algorithm fails on some colors. for example it fails for red (#FF0000). the text is not readable – Mahmoud Moravej May 31 '15 at 09:34
-
2seems that change the `factor` to 180 in `invertColor` function and it will look great! – Shu Ding Feb 25 '16 at 04:38
I linked another Question to this topic in the comments.
JS function to calculate complementary colour?
As Tejasva said you need to need to convert RGB to HSL, complement the Hue and convert it back.
I implemented the linked answer as a sample. Please upvote the original poster if this was helpful for you, because they provided the solution in fact.
Sample
http://jsfiddle.net/pLZ89/2/
-
1That is a sample/demo. I guess it should be easy for anyone to add a check if the color is black or white. – Smamatti Feb 09 '15 at 12:36
I had the same issue once, and gathered information all over the internet also using Salman A answer, I came up with this function, it supports hex, rgb and rgba
var invertColor = function (color) {
var hex = '#';
if(color.indexOf(hex) > -1){
color = color.substring(1);
color = parseInt(color, 16);
color = 0xFFFFFF ^ color;
color = color.toString(16);
color = ("000000" + color).slice(-6);
color = "#" + color;
}else{
color = Array.prototype.join.call(arguments).match(/(-?[0-9\.]+)/g);
for (var i = 0; i < color.length; i++) {
color[i] = (i === 3 ? 1 : 255) - color[i];
}
if(color.length === 4){
color = "rgba("+color[0]+","+color[1]+","+color[2]+","+color[3]+")";
}else{
color = "rgb("+color[0]+","+color[1]+","+color[2]+")";
}
}
return color;
}
but I don't think this is what you need, I found something more interesting, the below function will return white or black, it will decide witch one is more readable on the given color.
var getContrastYIQ = function (color){
var hex = '#';
var r,g,b;
if(color.indexOf(hex) > -1){
r = parseInt(color.substr(0,2),16);
g = parseInt(color.substr(2,2),16);
b = parseInt(color.substr(4,2),16);
}else{
color = color.match(/\d+/g);
r = color[0];
g = color[1];
b = color[2];
}
var yiq = ((r*299)+(g*587)+(b*114))/1000;
return (yiq >= 128) ? 'black' : 'white';
}
I don't take credit for any of this, I just got inspired and modified to my needs.
Sources: YIQ function explained

- 588
- 1
- 4
- 14
A bit late to the party, but IMO all text should either be light or dark. Colored text is for links.
Here's a coffeescript function I've written to decide which to use:
is_light = (hex_color) ->
c = hex_color.replace(/^#/,'')
sum = parseInt(c[0]+c[1], 16)
sum += parseInt(c[2]+c[3], 16)
sum += parseInt(c[4]+c[5], 16)
log "sum is #{sum}"
sum > 382.6

- 4,225
- 2
- 37
- 40
var getContrastYIQ = function(color) {
var hex = '#';
var r, g, b;
if (color.indexOf(hex) > -1) {
r = parseInt(color.substr(1, 2), 16);
g = parseInt(color.substr(3, 2), 16);
b = parseInt(color.substr(5, 2), 16);
} else {
color = color.match(/\d+/g);
r = color[0];
g = color[1];
b = color[2];
}
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
return (yiq >= 128) ? 'black' : 'white';
}
Thanks for the post ZetCoby! Had to adjust your "color.substr(" array position to account for the initial '#'; then it worked great! you could also use a color.replace('#',''); within the if block...

- 21
- 1
You could use this simple schema to achieve that goal. Just convert the color from RGB to HSV form. You can use this link . Then use this pseudo code;
rr = (color>>16)&0xFF;
gg = (color>>8)&0xFF;
bb = color & 0xFF;
someBrightColor = 0xFFFFFF;
someDarkColor = 0x000000;
hsvColor = rgbToHsv( rr, gg, bb );
//
//hsv is array: [h,s,v]...all values in [0,1]
//
//color is from dark range, if hsv < 0.5, so we need bright color to draw text, because in dark color bright color 'will be more visible'.
if( hsvColor[2] <= 0.5 )
textColor = someBrightColor ;
//this is opposite case , when in more bright color, the dark color 'will be more visible'
else
textColor = someDarkColor ;
Also you could divide [0,1] range into more parts. And instead of defining 2 colors (someBrightColor,someDarkColor) , define more colors. My suggested method is 'how bright is background color , thas text color must be dark, and vice versa'.

- 47,849
- 12
- 88
- 91
-
`rgbToHsv() function` accept decimal notation `[0, 255]` not hex. But assuming that I can make this translation, I really don't understand your pseudo-code. Please explain a little more. THX! – Igor Parra Mar 07 '12 at 11:50
-
Very clear. OK. I will implement all and inform you back. Meanwhile an upvote for the effort, THX! – Igor Parra Mar 07 '12 at 12:17
Here is a very small way to complement the hex value //hex value like "aa00cc"
function complementHex(hexValue){
var reqHex = "";
for(var i=0;i<6;i++){
reqHex = reqHex + (15-parseInt(hexValue[i],16)).toString(16);
}
return reqHex;
}

- 1,342
- 1
- 10
- 19
-
Unfortunately this has the same problem as the top answer above, i.e. Works really well for most colors but - as the note above suggests - not for all. For grey (#7F7F7F) it returns (#808080), which is practically identical. "Close but no cigar" :( – Zeek2 Feb 06 '17 at 15:35
var getContrastYIQ = function(color) {
var hex = '#';
var r, g, b;
if (color.indexOf(hex) > -1) {
r = parseInt(color.substr(1, 2), 16);
g = parseInt(color.substr(3, 2), 16);
b = parseInt(color.substr(5, 2), 16);
} else {
color = color.match(/\d+/g);
r = color[0];
g = color[1];
b = color[2];
}
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
return (yiq >= 128) ? 'black' : 'white';
}
var getContrastYIQ = function(color) {
var hex = '#';
var r, g, b;
if (color.indexOf(hex) > -1) {
r = parseInt(color.substr(1, 2), 16);
g = parseInt(color.substr(3, 2), 16);
b = parseInt(color.substr(5, 2), 16);
} else {
color = color.match(/\d+/g);
r = color[0];
g = color[1];
b = color[2];
}
var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
return (yiq >= 128) ? 'black' : 'white';
}
-
Why duplicating the snippet? It would be great to add some supporting information. – sm3sher Jul 17 '22 at 20:30
You want to keep at least some difference between the start and the end color. This becomes an issue for the rgb values in the center, if you take the opposite color, so for those values it's a bad idea to take the oposite values, as the contrast will go down, the closer you get to the middle(127-128). Thus here is some code that keeps that in mind, to keep at all time at least a set difference between the starting number and the result, if it can't respect the set minimum brightness difference, it will take 0 or 255, for the r g b value in question...
let say we have the color with the values { r: 216, g: 67, b: 130 }
the function below will return { r: 39, g: 195, b: 2 }
as result.
If black or white mode is set to true, it will return only black or white color.
function getGoodContrastColor(options) {
// check if black or white mode is enabled then check if total brightness values is greater than 255 * 3 / 2, if so, take black, else take white
let r = options.blackOrWhiteMode == true ? options.r + options.g + options.b > 255 * 3 / 2 ? 0 : 255 : null;
let g = options.blackOrWhiteMode == true ? options.r + options.g + options.b > 255 * 3 / 2 ? 0 : 255 : null;
let b = options.blackOrWhiteMode == true ? options.r + options.g + options.b > 255 * 3 / 2 ? 0 : 255 : null;
console.log('rgb values after the first condition', { r, g, b });
// check if the oposite color respects the minimum brightness difference, if so, take that
r = r != null ? r : Math.abs(options.r - (255 - options.r)) >= options.minimumBrightnessDifference ? 255 - options.r : null;
g = g != null ? g : Math.abs(options.g - (255 - options.g)) >= options.minimumBrightnessDifference ? 255 - options.g : null;
b = b != null ? b : Math.abs(options.b - (255 - options.b)) >= options.minimumBrightnessDifference ? 255 - options.b : null;
console.log('rgb values after the second condition', { r, g, b });
// check if the color + minimum brightness difference is smaller or equal to 255, if so, take that
r = r != null ? r : options.r + options.minimumBrightnessDifference <= 255 ? options.r + options.minimumBrightnessDifference : null;
g = g != null ? g : options.g + options.minimumBrightnessDifference <= 255 ? options.g + options.minimumBrightnessDifference : null;
b = b != null ? b : options.b + options.minimumBrightnessDifference <= 255 ? options.b + options.minimumBrightnessDifference : null;
console.log('rgb values after the third condition', { r, g, b });
// check if the color - minimum brightness difference is greater or equal to 0, if so, take that
r = r != null ? r : options.r - options.minimumBrightnessDifference >= 0 ? options.r - options.minimumBrightnessDifference : null;
g = g != null ? g : options.g - options.minimumBrightnessDifference >= 0 ? options.g - options.minimumBrightnessDifference : null;
b = b != null ? b : options.b - options.minimumBrightnessDifference >= 0 ? options.b - options.minimumBrightnessDifference : null;
console.log('rgb values after the fourth condition', { r, g, b });
// check if the color is greater than 127, if so, take 0, else take 255
r = r != null ? r : options.r > 127 ? 0 : 255;
g = g != null ? g : options.g > 127 ? 0 : 255;
b = b != null ? b : options.b > 127 ? 0 : 255;
console.log('rgb values after the fifth condition', { r, g, b });
return { r: r, g: g, b: b };
}
getGoodContrastColor({ r: 216, g: 67, b: 130, minimumBrightnessDifference: 128 });
getGoodContrastColor({ r: 216, g: 67, b: 130, minimumBrightnessDifference: 128, blackOrWhiteMode: true });

- 710
- 11
- 15