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.

Did you find this post useful? Show some love!

Hey there, we see you aren't signed in.

Please consider creating an account on dev.to. It literally takes a few seconds and we'd appreciate the support so much. ❤️

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)+'%)';

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.

// 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'));

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.

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

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().

I put it in a Codepen for you:

fork it to your account :-)

This would be great if my color choices had any logic to them 😂😂😂

No, seriously this is amazing ❤️

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))

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 😅

"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

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

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!

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.

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)


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

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.

This is when I need the 🤔 reaction back... 😂

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.


Classic DEV Post from Mar 30

Dear dev.to, I made it.

I took your advice.

Follow @shripathy to see more of their posts in your feed.