Customizing colour for web experiences
Implementing custom color schemes in web applications using CSS variables, and OKLCH
After evaluating customer requirements and the available market solutions, I narrowed solutions down to the following:
-
Allow customers to create their own components for building a web experience for their customers - While this is already possible through the REST API, most of our customers sell desktop software and are not well-versed with web technologies, and are unlikely to have their own design systems or UI libraries. This implies that we will have to find/build a low-code solution like Retool to allow our customers to build their components with great flexibility. We did not explore this further due to the high effort and low confidence of the initiative.
-
Allow customers to set a colour palette for our components - We gathered that most, if not all our customers would be able to set a colour palette for us to mirror their visual identity to some extent. This is a common pattern that we noticed in similar businesses like Paddle. However, when working with RGB, this approach can cause serious usability issues and requires our customers to do a fair share of visual testing because our components are built on interaction rules that might not work for all colours. With the increasing browser support for OKLCH and the predictable lightness it provides, it would be perfect for a use case such as this. It would prevent accessibility issues while however compromising on the perceived hue.
Using OKLCH looked straight-forward and it seemed that it would allow building a preview of the colour change in a relatively simple manner.
OKLCH implementation
Since the OKLCH solution, in theory, required the lowest effort, I decided to build a proof of concept for the same. The resulting color palettes/ramps were to be used with Angular Material.
Adam Argyle shows a modern and performant approach to generating colour palettes in a web application with OKLCH, that I found to be perfect for the use case.
/// Generates an OKLCH palette in CSS variables for a given name and hue.
@mixin generate-variables-for-hue($name, $value) {
--#{$name}-hue: #{$value};
// Generate palette swatches
--x#{$name}-swatch-1: oklch(0.99 0.02 var(--#{$name}-hue));
...
--x#{$name}-swatch-10: oklch(0.05 0.13 var(--#{$name}-hue));
// Reference to palette swatches. Required for dark mode switch.
--#{$name}-swatch-1: var(--x#{$name}-swatch-1);
...
--#{$name}-swatch-10: var(--x#{$name}-swatch-10);
}
/// Swap Swatch 1 & Swatch 10, Swatch 2 and Swatch 9, and so on.
@mixin generate-dark-mode($name) {
--#{$name}-swatch-1: var(--x#{$name}-swatch-10);
...
--#{$name}-swatch-10: var(--x#{$name}-swatch-1);
}
The Angular documentation website has a ‘theme’ switching mechanism that swaps out multiple bundles of CSS to switch themes. All of these bundles contain the same rules with a different set of colours, set using a SCSS map at compile time. This approach has no regard for performance.
With the integration with Angular Material, I faced the following problems:
- Angular Material determines colour from a Sass map created by the
mat.define-light-theme($theme)
ormat.define-dark-theme($theme)
. This approach to creating your own palettes recommended by the Angular Material documentation gives no control over how the background and foreground colours are set. By studying the map generated bymat.define-#-theme()
, I had to build the map manually, which owes to how inflexible the UI library is. - A lot of the properties in the map are hardcoded like shadow colors, density, and input field borders. This forced me to create a separate CSS bundles for dark and light mode.
To my disappointment, the density cannot be set using a CSS variable which would have come in handy to create a Relaxed, Normal, and Compact view switch.
Next steps
-
The form that allows changing hue could possibly show a little more of a preview - While the current proof-of concept shows the colours change as the slider value is modified, setting the right context for this change could provide a better signal.
-
Integration with environments that don’t use OKLCH will need conversions to HEX - The OKLCH system is new to browsers and solves issues that RGB and HSL could not. However, since it is relatively new, adjustments in Design Token stores like Figma, Style Dictionary, and even the W3C Design Token specification are yet to be made. Currently, the only way to continue working with these libraries would be to convert between OKLCH and RGB/HSL.