Accessibility of text over random background colors

Investigating issues which might result in poor accessibility with text drawn over a random background color

Introduction

One of the important parts of application development is to make it accessible to as many users as possible. This includes people with impaired vision, color blindness, impaired hearing, impaired dexterity, cognitive disabilities, and many other disabilities.

Making an app accessible is challenging but rewarding. The difficulty is because the development team should pay attention to some details which often might be overlooked. Though besides some advanced accessibility support might be too expensive to add, there are still some basic best practices to ensure that your app development is heading in the right direction (and can be improved later over time).

Basic accessibility support includes providing content descriptions, large enough touchable areas and ensuring good color contrast between foreground and background components.

In this article, we’ll go in detail on the latest — color contrast. We’ll take a look at one specific situation I faced while developing FiberyUnofficial — my pet project, Android companion app for https://fibery.io.

Toolbar background

In Fibery (as in I guess any other similar tool) each entity has its color. The system generates one by default when a new entity type is created and then the user is free to change it. There is a screen on the web and in the app to see details of some particular entity (i.e. view all the fields).

Of course, the best way to add “entity personality” to the entity details screen is to color it with the entity’s color. So, we’ll have a toolbar on top of the screen with the entity color background and text (with entity type name and id) on top.

This is how it might look like:

NOTE: as Android theming doesn’t support dynamic creation, we’ll have to create everything manually. The good news is that for the toolbar we have all the needed methods to change the background and text colors dynamically.

Also there is a way to add tint over images, so action icons in the toolbar can be tinted dynamically as well.

We change the toolbar color with:

toolbar.setBackgroundColor(bgColor)

This looks good, but it is just because the background color is light so black text looks good. If we have a dark background color then it won’t look nice:

Text color

To fix this we need to apply simple logic.

if (bgColor.isDark) {

toolbar.setTitleTextColor(Color.WHITE)

} else {

toolbar.setTitleTextColor(Color.BLACK)

}

The only thing left is to define how to understand whether bgColor is dark or light.

Alternative color models

The most common color model is RGB, I guess we all know it. It consists of three channels: one for red, second for green and third for blue. Changing values in channels result in a different color.

But there are also other color models, among which there are HSL, HSV, etc.

Here we’re interested in XYZ.

Y parameter (which is luminance) is exactly the parameter we need to identify whether our color is dark or light. This value is defined in a range [0, 1] , where 0 means the darkest color and 1 the lightest one.

Luckily for us, we don’t need to do the color conversion ourselves, there is a handy utility method in the Android SDK already:

ColorUtils.calculateLuminance(bgColor)

It will return a float value between 0 and 1. The simplest logic of defining whether bgColor is dark or not is to compare with the middle value — 0.5:

fun isDarkColor(@ColorInt color: Int): Boolean {

return ColorUtils.calculateLuminance(color) < 0.5

}

NOTE: this is exactly what one can find on StackOverflow after searching for dark color checking.

And finally the result:

Dark theme support

What we have already looks good, but at the same time we still have an issue if we decide to add support for dark theming, check it out:

Our toolbar looks too bright. And it is because of saturation of the color is too high. If we decide to follow design guidelines for a dark theme we’ll see that we need to de-saturate our color to make it “softer” for eyes when dark mode is enabled.

What it means that we need to convert our RGB color into HSL or HSV (translated as hue, saturation, value) and reduce the second parameter responsible for saturation. Then convert the color back to RGB and use it as the background of toolbar.

This time we’ll do some manual work by creating a special method for de-saturation, which under the hood will convert our color to HSV (as there is ready method for that in the Android SDK):

@ColorInt

fun getDesaturatedColor(@ColorInt color: Int): Int {

val result = FloatArray(size = 3)

Color.colorToHSV(color, result)

result[1] *= 0.6

return Color.HSVToColor(result)

}

We’ll multiply saturation by 0.6. It is just the experimentally taken value.

NOTE: probably an improvement to the approach could be to not only decrease ther saturation of all colors, but to also add some lower bound limit to not de-saturate already “de-saturated” colors that much

And here is the result (with comparison: original on the left and de-saturated on the right):