DEV Community

Ben Halpern
Ben Halpern

Posted on

Write a function that outputs a pleasant text color hex given a background color hex

"Pleasant" means a high enough contrast for reading, but also might include colors that don't clash with one another.

Do your best. No harsh judgement. 😝

Any languages/approaches welcome.

Top comments (20)

elmuerte profile image
Michiel Hendriks • Edited

Basically rotate the HSL components ((value+0.5)%1). The complicated part is converting a rgb() to hsl().

function niceColor(rgb) {    // assumes "rgb(R,G,B)" string
  let hsl = rgb2hsl(rgb);
  hsl[0] = (hsl[0]+0.5) % 1; // Hue
  hsl[1] = (hsl[1]+0.5) % 1; // Saturation
  hsl[2] = (hsl[2]+0.5) % 1; // Luminocity
  return 'hsl('+(hsl[0]*360)+','+(hsl[1]*100)+'%,'+(hsl[2]*100)+'%)';

elmuerte profile image
Michiel Hendriks

Note that only changing the luminosity part (hsl[2]) is vital. Disabling the other parts (hue and saturation) also creates pleasant results, just less colorful.

nektro profile image
Meghan (she/her) • Edited
// assumes bgColor is a rgb() string: "rgb(66, 134, 244)"
function getTextColor(c) {
  let rgb = c.substring(4, bgColor.indexOf(')')).split(", ").map(x => parseInt(x));
  let o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) /1000);
  return ((o > 125) ? ('black') : ('white'));
ben profile image
Ben Halpern

So this returns black or white, but what if the ideal solution returned, perhaps a very dark version of the background color. Like, dark blue on light blue might be more pleasant than black on light blue etc.

nektro profile image
Meghan (she/her)

Would this work then? get color, convert color to HSL, return L - 100 % 100

cathodion profile image
Dustin King • Edited

I decided to use machine learning...

14 hours later 😬, here's a work in progress for a script that randomly generates a background color, then generates the foreground color from a neural net that learns from your selections.


Converted to codepen by @rhymes :

The gist is still here but I'll take the embed out since gist embeds load slowly and take up a lot of screen real estate.

Edit 2: I cleaned up the code in both places so hopefully it's more readable now. The bulk of the neural stuff happens in regenerateColors() and trainOnCurrentColors().

rhymes profile image

I put it in a Codepen for you:

fork it to your account :-)

ben profile image
Ben Halpern


booligoosh profile image

This would be great if my color choices had any logic to them πŸ˜‚πŸ˜‚πŸ˜‚

No, seriously this is amazing ❀️

aralroca profile image
Aral Roca • Edited

"Pleasure" is a little bit subjective. Basically I based with the complementary color to be sure that the text is possible to read:

Complementary colors

const complementaryColor = (color) => {
    const hexColor = color.replace('#', '0x');

    return `#${('000000' + (('0xffffff' ^ hexColor).toString(16))).slice(-6)}`;

complementaryColor('#ff0000');  // #00ffff
aralroca profile image
Aral Roca

React example with random color of background color + complementary for text:

itsasine profile image
ItsASine (Kayla)

I mainly was having fun with research for this. Color theory, the parts of RGB color, playing with jQuery...

Known bug: the contrast for Cyan sucks.

Known feature enhancement: provide better suggestions than just white or black πŸ˜…

mxl profile image
Maria Boldyreva • Edited

I implemented this in python:

import argparse
from colorsys import hls_to_rgb, rgb_to_hls
import sys

# setting up arguments
parser = argparse.ArgumentParser(
    description='Function that outputs a pleasant text color hex '
                'given a background color hex')
parser.add_argument('color', help='Color in #xxxxxx or xxxxxx format')

args = parser.parse_args()
color = args.color

# removing #
if color.startswith('#'):
    color = color[1:]

# splitting, converting to decimal and then to 0-1 range
    r, g, b = [int(color[i:i+2], 16) / 255.0 for i in range(0, len(color), 2)]
except ValueError:
    print 'Wrong color supplied'

# shifting
h, l, s = [(i + 0.5) % 1 for i in rgb_to_hls(r, g, b)]

# converting back to rgb
result = [format(int(i*255), 'x') for i in hls_to_rgb(h, l, s)]
print '#{}'.format(''.join(result))
jessachandler profile image
Jess Chandler • Edited

I wasn't sure what we should do with "pleasant", so I just created little functions to adjust the color

function adjustHex(y) { 
  const x = parseInt(y, 16); 
  if (x<50) {
    return '00';
  } else if(x<=200) {
    return (x-50).toString(16);
  } else {
    return (255).toString(16); 

function splitColor(x) { 
  while (x.length <6) { 
    x = '0' + x 
  if (x=='000000') {
     return 'ffffff' 
  } else if (x=='ffffff'){
    return '000000'
  } else {
    let y = x.split(/(?=(?:..)*$)/); 
    return adjustHex(y[0])+adjustHex(y[1])+adjustHex(y[2]);

The logic is that if there is an RGB close to FF, then, max that out. If there is one close to 00, then zero that out. If it is in between, lighten it. If the background is black or white, return the opposite.

for example
splitColor('ff00aa') returns "ff0078"
splitColor('ffffff') returns "000000"

There are some edge cases that I did not address - every one that is a combination of extremes. I did not create this in codepen, and now I am kicking myself for just whipping it up in the javascript console while I was debugging something else!

andrewlucker profile image
Andrew Lucker • Edited

I once attempted this problem for setting up a color scheme for a website. Turns out "pleasant" depends on the size of inner/outer elements. The problem is called "color relativity". People don't physically identify colors based on RGB wavelengths, instead we look at contrast with surrounding context. If there is very little contrast, we can perceive very slight differences in absolute color value. If there is very high contrast, we can only perceive significant differences. Also context can "shift" colors to look the same. For example:

High contrast: At night, with house or street lights. Most people become nearly color blind.
Low contrast: Snow blind. People can visually identify physical shapes of hills, roads, trees etc. despite everything being white.

miionu profile image
Rospars Quentin • Edited

I made a black and white version. I'll try to work on it a bit more later

(Click on the background to change the color)

jrohatiner profile image
Judith • Edited

Caveat: this was already done :P and I hope that the rgb(0,0,0) text being only one color (non-color: black is really no color) is considered pleasant :) That being said; the input of a hex generates a color in the box with black text showing the rgb of the hex color. I used js, jquery, html and css :) Namaste!

ps there are two functions

bladefidz profile image
Hafidz Jazuli Luthfi • Edited

To be honest, I am sorry to not post the code, but just underlying theory. Based what I already learn:

  1. Blue is not good for text color, so try to adding more proportion on Green or Red. Good one proportion is L = 31%R + 59%G + 10%B.
  2. 10% males are color blind and there are more man who code than women, so try to use font with sub-pixel rendering. Sans-serif family may the good one. Another good trick is try to adding more contrast.
  3. Eye can be exhausted, so try to use cooler color as background and sharp color for details such as text and button.

I am also attach an image below to help you interpret:
Color Perception

That's all, hope somebody find this post useful.

yechielk profile image
Yechiel Kalmenson

This is when I need the πŸ€” reaction back... πŸ˜‚

ben profile image
Ben Halpern

I think it'll be back at some point. The underlying logic allows for arbitrary reactions but the proper UI direction is not so obvious.