When it comes to accessibility, the most common errors are contrast errors. And with an auto-contrast function in SCSS they seem to be fixed easily. But unfortunately, the vast majority of color contrast functions don’t work they way they should. At least not according to the WCAG 2 specifications.

Typically, if you use SCSS and a function or mixin to automatically calculate a contrast color (e.g. given the background color automatically set the text color to either white or black), it looks like this:

$threshold: 55%;

@function contrast-color($color, $dark, $light) {
  @return if(lightness($color) < $threshold, $light, $dark)
}
Code language: SCSS (scss)

This is the first result I found on Google. You give the function a color as well as a dark and a light (text) color and depending on the lightness of the first given color, it either returns the light or the dark color.

This goes well for a majority of colors, but not for all of them. I stumbled upon this here at Epiphyt, where I use buttons with the background color #1b978b. The lightness of this color is 35 %, which means it is below the threshold in the function above and thus the light text color will be returned. However, according to the WCAG 2 specification, white text on such a background is only allowed for large text, since its contrast ratio is only 3.55 (for regular content, it should have at least 4.5).

Color contrast: AA Large (ratio 3.55) via ColorShark

The problem here is that lightness is not the only value to use in the contrast calculation. In fact, the specification is much more complicated, as you can see by following this link:
https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef

Luckily, Tony Pinel already came with a solution that implemented this calculation in SCSS:
https://medium.com/@tonipinel/how-to-get-an-accurate-contrast-color-with-sass-b0ebc01bf17f#.jrfrion7k

Since his post is eight years old at time of writing, it doesn’t consider the current Dart Sass implementation, which uses the sass:math functions. Since this is the preferred way to compile SCSS to CSS and also being used by @wordpress/scripts for blocks, I changed his code to match the preferred implementation. This looks like this:

@use "sass:math";

@function de-gamma($n) {
	@if $n <= 0.03928 {
		@return math.div($n, 12.92);
	}
	@else {
		@return math.pow((math.div(($n + 0.055), 1.055)), 2.4);
	}
}

@function re-gamma($n) { 
	@if $n <= 0.0031308 { 
	   @return $n * 12.92; 
	} @else { 
	   @return 1.055 * math.pow($n, math.div(1, 2.4)) - 0.055;
	}
 }

// sRGB BT-709 BRIGHTNESS
@function brightness($c) {
	$rlin: de-gamma(math.div(red($c), 255));
	$glin: de-gamma(math.div(green($c), 255));
	$blin: de-gamma(math.div(blue($c), 255));
	@return re-gamma(0.2126 * $rlin + 0.7152 * $glin + 0.0722 * $blin) * 100;
}

// Compares contrast of a given color to the light/dark arguments and returns whichever is most "contrasty"
@function contrast-color($color, $dark: #000, $light: #fff) {
	@if $color == null {
		@return null;
	}

	@else {
		$color-brightness: brightness($color);
		$light-text-brightness: brightness($light);
		$dark-text-brightness: brightness($dark);

		@return if(abs($color-brightness - $light-text-brightness) > abs($color-brightness - $dark-text-brightness), $light, $dark);
	}
}
Code language: SCSS (scss)

You can then use the function contrast-color() the same way as the first example, but with an accurate contrast calculation.

3 comments

  1. This was very helpful thank you — in the middle of migrating our application to WCAG compliance.

    1. It seems that CodePen doesn’t support the Dart Sass implementation used here. Checkout the linked article of Tony Pinel, that should be usable for you.

Leave a Reply

Your email address will not be published. Required fields are marked *