PostCSS Relative Color Syntax

We are happy to announce the release of @csstools/postcss-relative-color-syntax, a PostCSS plugin to use relative color syntax in any browser.

.example {
	background: oklab(
		from oklab(54.3% -22.5% -5%)
		calc(1.0 - l)
		calc(a * 0.8)

This plugin uses @csstools/css-color-parser under the hood to parse the color values, checkout the blog post for all the technical details.

If you are using postcss-preset-env you will already have this plugin when using the latest version.

Color palette demo

Relative color syntax is especially useful for creating color palettes.

Use these controls to alter the center color swatch.
All surrounding colors are generated from that single color.

How the demo works :

sin curves

All the colors are generated from a single color by using sin.
sin curves go up and down and up and down again.
They are not as nice as bell curves, or tailored cubic Bézier curves, but sin is simple to work with in CSS.

This effect can be used to smooth value progression or to create "toggles".

controlling chroma

We do not want to have a high chrome when nearing black or white as that would lead to excessive clamping in browsers without support for gamut mapping. Using sin to control the chroma, we can have a lower chroma when nearing black or white.

keeping contrast high

For the text color we actually want to use contrast-color() but the specification isn't final yet. So we use something simpler that will work often enough.

When the color has a lightness over 0.5 we toggle to black, when it is under 0.5 we toggle to white. We clamp the result a bit as pure white and black might be too harsh.

All these values are able to be controlled with relative color syntax. Without this new CSS feature it would not be possible to easily create derivative colors.

secondary colors

The secondary colors are generated by adding and subtracting a number of degrees from the primary hue. Depending on the angle this gives a harmonic or more eclectic palette.

.swatch {
	/* NOTE :
		postcss-preset-env and other plugins 
		can not generate fallback values when `var()` is used.

		But using custom properties in this example
		makes it clear which values change and which are static.

	/* Your starting color: */
	--palette-start-color: hotpink;
	/* The lightness of this swatch */
	--lightness: 50%;
	/* Chroma factor is inverted, higher number is less chroma */
	--chroma-factor: 10;

	--background-color: oklch(
		/* every swatch has the same start color */
		from var(--palette-start-color)
		/* the lightness is the variable */
			the chroma is a function of the lightness,
			using a smooth curve that is lower when approaching white and black
		calc((sin((l * 2 * PI) + (-1 * (PI / 2))) + 1) / var(--chroma-factor))
		/* hue is taken from the palette start color */
	color: oklab(
		/* foreground color starts from the background color */
		from var(--background-color)
			lightness is a function of the background color lightness:
			- any value over 0.5 will be clamped to 0.9
			- any value under 0.5 will be clamped to 0.2

			Clamp is used because sub pixel rendering with overly bright colors seems to be bugged.
		clamp(0.2, ((sin((l * PI) + (PI / 2)) + 0.1) * 9999), 0.9)
	background-color: var(--background-color);