I've been studying a lot about web accessibility lately, and I noticed that one of the aspects that's often overlooked is color contrast. It’s particularly hard to prevent contrast issues in apps that allow the user to select the color scheme.
Many times, I’ve been in situations where there were real tugs-of-war between designers and accessibility experts. One side pushing for high contrast and the other pushing for beauty.
This got me thinking: there has to be a win-win solution
Some years ago, I wrote an article explaining how to create color harmonies with sass. What if I could use a similar approach to create a proof of concept? How can we allow the user to select a UI color scheme making sure that the colors have enough contrast?
Ok, so how does it work?
In the right panel, we have a form with a couple of inputs that allow the user to select a main color, a mode and a color scheme.
We should also save a MIN_CONTRAST so we can use it afterwards. According to the Web Content Accessibility Guidelines (WCAG) the contrast should be at least 4.5
Generating color schemes
If you’re not sure how this works, the way we manipulate colors today is based on the initial studies of color theory. By selecting one initial color in the wheel, we can rotate X degrees to get to another. And create harmonies based a formula, or color scheme:
With that in mind, we can create the markup for the form that will allow the user to manipulate the UI, starting with an input for the main color:
By including the type=”color” in the input, we have access to the browser’s color picker. We can then use a v-model=”mainColor” to save the user input in our data object. Then, we can trigger an updateColors method every time the value changes.
Next, we have some radio inputs that allow the user to select the colorScheme:
Every time the user selects a different color scheme, we point to a new object inside the colorSchemes object. Each color scheme contains its name and 3 angles:
Considering that the color scheme will always have 4 color slots. We can use one mainColor ( selected by the user ) + 3 colors based on the angles of the selected color scheme.
You’ll notice that in the example above, some angles have repeated numbers. This is so we can fill in the 4 slots even for schemes that contain fewer colors. The Complementary scheme, for instance contains colors that sit across from each other on the wheel. so we would only have the initial color ( or that color rotated by 0° ) and another one, rotating the initial color by 180°.
Once we have a mainColor and the angles in the selectedColorScheme, we can use the Color package in computed properties to automatically get the secondary, tertiary and quaternary colors:
Testing color contrast
What we've talked about so far allows the user to select color schemes, but to make sure that they are accessible, we should test for color contrast between the background and text.
So instead of considering just one color for each slot, we should consider two colors per slot: one for the background and one for the text.
By refactoring the updateColors method, we can generate both text and background colors as welll as check the contrast. Then, we can update the CSS:
the generateTextAndBg method will return an object with both text and background colors, after achieving a combination that has enough contrast:
With this approach, we can guarantee enough contrast by using black or white text. But what if we wanted something a bit different?
Taking it one step further
We can use the original color and darken/lighten it until we have enough contrast instead of just using black and white:
About design constraints
If we know that in our UI, we have certain constraints, we can use that in our favor to implement dynamic colors easily. In the prototype we created for instance, product cards always have white backgrounds with some dark gray text and a link . Since we want the links to be visually distinguished from the other text information, and we know it's always going to be on a white background, we can generate the link colors based on the quaternary color available.
By using the quaternary for links, we guarantee that the link color is always going to be different from the main color, no matter if the scheme selected has repeating slots or not.
Another example of useful design constraints is toggling between dark and light mode. We can watch for changes on the mode selection, and use hardcoded text and background color values:
Making images match the color palette
In this prototype, you'll notice that the banner image colors also change depending on what the user selected:
To achieve this, we simply use an element covering the banner image, that has .4 opacity and a linear-grading from the tertiary to the quaternary colors:
That’s all, folks
This example is far from perfect, but hopefully some of the techniques explored here will help us build more accessible interfaces.
The code for the prototype is on github, feel free to contribute to it either by submitting a pull request or suggesting any changes :