This is actually a draft…

Disclaimer: This post is about User Interfaces for desktop applications. Some of the things stated here may not apply to mobile applications and are usually not possible to do on the web.

I assume everyone who has used a dark color theme, at some point, came across something as readable as this:

Screnshot of User-Interface with unreadable color combinations
In this article I am going to present an algorithm which could have made the above UI look more like this:

Mockup of how the above UI should look"
Before that, however, I want to show you that with proper programming you might not even need said algorithm.
First let’s analyze which mistakes were likely made in the above example.

  • In the table, the dark grayish background is the system default. The font color is a black, which of course would be perfectly fine on a bright background. The system default would be a light gray, so black is most likely hardcoded.
  • Right of “Zustand:” we have light gray on light green. Green, i think, is chosen be the developer to symbolize something positive, while the light gray is the system’s default font color.
  • Below that we have more of the same, just with a neutral white instead of the positive green.

The pattern here is easy to spot: one color is defined by the system, the other by the developer. Testing is only done with a bright color theme.

How can we to avoid this?

First of all, you should use combinations of system-defined colors whenever possible and never mix system colors with hardcoded colors.
While that is certainly improving readability, there is still one major flaw with that guideline. It implies that it’s ok to hardcode colors which is rarely the case.
Most people never change their systems colors, but those who do, do it for a reason. That reason may just be because ‘it looks cool’, but it may as well be something more important like reducing eye strain (though there does not seem to be a definite conclusion but the general consensus seems to be that dark-on-light-high-contrast themes improve readability, while light-on-dark-medium-contrast themes reduce strain on the eyes during long working hours, but ultimately it comes down to personal preference) and in some cases because they are visually impaired and would otherwise be unable to use a computer.

Bottom line is: hardcoded colors decrease the quality of your user interface.
Best case: it will look slightly wrong in context of the users desktop, worst case: it will make your software unusable for someone. In my opinion this is not worth it, especially since it’s so easy to fix :).

How to do it better?

  1. Use system defined colors, whenever possible. Usually, you don’t need to do anything for this to happen (on linux, due to 2+ “default” toolkits, a little work on the user side might be necessary). If the UI toolkit you’re using is unable to use system colors (note that some toolkits can only do this on some platforms), I would consider it broken by design (on that platform) and strongly suggest, you use something else.
    • If you’re developing a commercial application, you might consider using a theme based on your company’s colors. If you do that, I am sure someone will hate it. For that reason my best advice is: don’t! If you feel that you have to, at least give your users the option to switch to system default.
  2. For any color, where there is no system value (note that KDE offers more choices here…), make an entry in your applications settings dialog, where the user can change both, your color (C_Own) and the color it is used with (C_Sys).
  3. The first time a user starts you application, set all C_Sys to system default for their role. Then run the algorithm: C_Own = colorFor(C_Sys, [your preffered color]). This will check whether your choice is readable. If so, C_Own will be your color, otherwise it will be set to a color with sufficient contrast to C_Sys.
  4. Test your UI! At least on a bright, a dark and a high contrast color theme. And while you’re at it, raise your font size to 40pt and test it again. This may mess up your layout, but if you want everyone to be able to use your software, you need to fix that. After all, some people simply can’t read small letters.

Test it!

(click the color buttons below – might not work on Safari and mobile browsers)

Download as Javascript

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ac tempus tortor, et facilisis dui. Curabitur quis vehicula massa. Cras iaculis porttitor aliquet. In hac habitasse platea dictumst. Aliquam bibendum nisi in ex ullamcorper sollicitudin. Sed et lacus a ipsum consectetur tempor.

Aenean pharetra feugiat commodo. Nunc id tortor sit amet ante tincidunt aliquet et vel mauris. Quisque eu est eros. Nulla lobortis pellentesque velit, ut imperdiet felis finibus nec. Maecenas lectus sapien, consequat dapibus turpis eget, eleifend fringilla magna.



So… how do I know if my text is readable?

Well… to be readable, two colors need to have enough contrast between them. But how can we get a number for that?

First thing I tried, was comparing the avarage of two colors’ values for red, green and blue. The result wasn’t very satisfying.
Next was taking a difference in hue into account. Not much better. So some research into color perception was neccessary.
What got me on the right path was this article on color luminance.

Using float values for RGB

Red, green and blue are perceived as differently bright.

R = 0.2126
G = 0.7152
B = 0.0722

function luminance(color)
  return color.r * R + color.g * G + color.b * B

function colorFor(col, pref)
    l, lt, ok
  lt = 0.65 // threshold for difference in luminance - would be best as a system wide setting
  l = delta (luminance(col), luminance(pref))
  ok = ( l > lt )
  if (!ok)
    l = luminance(col);
    // get target luminance
    if (l > 0.5)
      l = min (l - lt, 0.0)
      l = max (l + lt, 1.0)
    pref = setLuminance(pref, l)
  return pref

function setLuminance(col, l)
       r, g, b, over;
    if (l <= 0.0)
        return BLACK
    if (l >= 1.0)
        return WHITE
    if (col == BLACK)
        col.r = col.g = col.b = 1.0/255.0;
    l = l / luminance(col);
    r = l * col.r
    g = l * col.g
    b = l * col.b
    if (b > 1.0)
      over = (b - 1.0) * B
      b = 1.0
      r += over * R
      g += over * G
    if (r > 1.0)
      over = (r - 1.0) * R
      r = 1.0
      g += over * G
    if (g > 1.0)
      g = 1.0
    col.r = r
    col.g = g
    col.b = b
    return col