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.

8 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. Check out the linked article of Tony Pinel, that should be usable for you.

  2. In our project, our SASS compiler is throwing deprecation warnings:

    "DEPRECATION WARNING: color.red() is deprecated. Suggestion:
    [toolkit] 
    [toolkit] color.channel($color, "red", $space: rgb)
    [toolkit] 
    [toolkit] More info: https://sass-lang.com/d/color-functions
    [toolkit] 
    [toolkit]    ╷
    [toolkit] 28 │   $rlin: de-gamma(math.div(color.red($c), 255));
    [toolkit]    │                            ^^^^^^^^^^^^^
    [toolkit]    ╵
    [toolkit]     src/global/functions/contrast-color.functions.scss 28:28  brightness()
    [toolkit]     src/global/functions/contrast-color.functions.scss 37:24  apply()
    [toolkit]     src/global/mixins/set-colors.mixin.scss 23:22             apply()
    [toolkit]     src/components/alert/alert.mixins.scss 57:3               status-success()
    [toolkit]     src/components/alert/alert.scss 26:5                      @use
    [toolkit]     src/dso.scss 13:1                                         root stylesheet
    [toolkit] 
    [toolkit] DEPRECATION WARNING: color.green() is deprecated. Suggestion:
    [toolkit] 
    [toolkit] color.channel($color, "green", $space: rgb)
    [toolkit] 
    [toolkit] More info: https://sass-lang.com/d/color-functions
    [toolkit] 
    [toolkit]    ╷
    [toolkit] 29 │   $glin: de-gamma(math.div(color.green($c), 255));
    [toolkit]    │                            ^^^^^^^^^^^^^^^
    [toolkit]    ╵
    [toolkit]     src/global/functions/contrast-color.functions.scss 29:28  brightness()
    [toolkit]     src/global/functions/contrast-color.functions.scss 37:24  apply()
    [toolkit]     src/global/mixins/set-colors.mixin.scss 23:22             apply()
    [toolkit]     src/components/alert/alert.mixins.scss 57:3               status-success()
    [toolkit]     src/components/alert/alert.scss 26:5                      @use
    [toolkit]     src/dso.scss 13:1                                         root stylesheet
    [toolkit] 
    [toolkit] DEPRECATION WARNING: color.blue() is deprecated. Suggestion:
    [toolkit] 
    [toolkit] color.channel($color, "blue", $space: rgb)
    [toolkit] 
    [toolkit] More info: https://sass-lang.com/d/color-functions
    [toolkit] 
    [toolkit]    ╷
    [toolkit] 30 │   $blin: de-gamma(math.div(color.blue($c), 255));
    [toolkit]    │                            ^^^^^^^^^^^^^^
    [toolkit]    ╵
    [toolkit]     src/global/functions/contrast-color.functions.scss 30:28  brightness()
    [toolkit]     src/global/functions/contrast-color.functions.scss 37:24  apply()
    [toolkit]     src/global/mixins/set-colors.mixin.scss 23:22             apply()
    [toolkit]     src/components/alert/alert.mixins.scss 57:3               status-success()
    [toolkit]     src/components/alert/alert.scss 26:5                      @use
    [toolkit]     src/dso.scss 13:1                                         root stylesheet
    [toolkit] 
    [toolkit] DEPRECATION WARNING: color.lightness() is deprecated. Suggestion:
    [toolkit] 
    [toolkit] color.channel($color, "lightness", $space: hsl)
    [toolkit] 
    [toolkit] More info: https://sass-lang.com/d/color-functions
    [toolkit] 
    [toolkit]    ╷
    [toolkit] 26 │     @if color.lightness($background-color) < 100% {
    [toolkit]    │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    [toolkit]    ╵
    [toolkit]     src/global/mixins/set-colors.mixin.scss 26:9  apply()
    [toolkit]     src/components/alert/alert.mixins.scss 57:3   status-success()
    [toolkit]     src/components/alert/alert.scss 26:5          @use
    [toolkit]     src/dso.scss 13:1                             root stylesheet"

    Do you already have plans to address this?

    1. Since I don’t have this problem so far, I suggest to do what the suggestion says and replace the color.red($c)etc. with its suggestion.

      1. thanks, but that results in:
        [ ERROR ] sass error: …lkit/node_modules/dso-toolkit/src/global/functions/contrast-color.functions.scss:28:28
        [core] [stencil] Undefined function.
        [core] [stencil]
        [core] [stencil] L27: @function brightness($c) {
        [core] [stencil] L28: $rlin: de-gamma(math.div(color.channel($c, “red”, $space: rgb), 255
        [core] [stencil] L29: $glin: de-gamma(math.div(color.green($c), 255));
        [core] [stencil]
        [core] [stencil] [56:34.5] rebuild failed, watching for changes… in
        [core] [stencil] 1.84 s

        “color” is highlighted.

  3. Hi Matze, I owe you an apology: we have now discovered that the dart-sass version bundled with our build tool is too old for the new color functions, so the fault lies with us. We are now trying to remedy that situation, after which we anticipate no further trouble updating our code.
    Thanks for your assistance!

Leave a Reply

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