Skip to content

Instantly share code, notes, and snippets.

@haschek
Last active August 14, 2019 14:20
Show Gist options
  • Save haschek/1059983 to your computer and use it in GitHub Desktop.
Save haschek/1059983 to your computer and use it in GitHub Desktop.
SCSS/SASS Accessibility Color Methods
/*
SCSS Color Methods for Accessibility
================================================================================
Adjust given colors to ensure that those color combination provide sufficient
contrast.
@version 0.1
@link http://eye48.com/go/scsscontrast
@license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License (LGPL)
@author Michael Haschke, http://michael.haschke.biz/
Usage
--------------------------------------------------------------------------------
Import contrast.scss first, then use method ``color_adjust_contrast_AERT``.
```(scss)
@import 'libs/contrast';
.my-element {
background-color: #eee;
color: color_adjust_contrast_AERT(#ccc, #eee); // get grey with good contrast on #eee
}
```
How it works
--------------------------------------------------------------------------------
It tests for sufficent difference on brightness and color of two values. If it
is not enough contrats regarding test formulas of accessible guidelines then one
color is adjusted on lightness and saturation.
Methods using calculations from:
* "Techniques For Accessibility Evaluation And Repair Tools (AERT)" working draft,
@see http://www.w3.org/TR/AERT#color-contrast
* TODO: add methods to test with WCAG2 formulas
*/
// -- AERT ----------------------------------------------------------------
@function color_adjust_contrast_AERT(
$color_adjust,
$color_keep,
$lightness_change_value:1,
$saturation_change_value:1,
$brightness_min: 75,
$difference_min: 500)
{
@if (color_test_contrast_AERT($color_adjust, $color_keep, $brightness_min, $difference_min) == true)
{
@return $color_adjust;
}
$color_save: $color_adjust;
$S: saturation($color_adjust);
$L: lightness($color_adjust);
$foundresult: false;
@while(($foundresult == false) and ($S > 0 or ($L > 0 and $L < 100)))
{
$color_adjust: desaturate($color_adjust, $saturation_change_value);
$S: $S - $saturation_change_value;
@if (lightness($color_keep) > lightness($color_adjust))
{
$color_adjust: darken($color_adjust, $lightness_change_value);
$L: $L - $lightness_change_value;
}
@else
{
$color_adjust: lighten($color_adjust, $lightness_change_value);
$L: $L + $lightness_change_value;
}
$foundresult: color_test_contrast_AERT($color_adjust, $color_keep, $brightness_min, $difference_min);
}
@if ($foundresult == false)
{
@debug $color_save + " was adjusted to " + $color_adjust + " but this is not enough contrast to " + $color_keep + " (AERT).";
}
@return $color_adjust;
}
@function color_test_contrast_AERT($color_1, $color_2, $brightness_min: 75, $difference_min: 500)
{
$difference_brightness: math_absolute(color_brightness_AERT($color_1) - color_brightness_AERT($color_2));
$difference_color: color_difference_AERT($color_1, $color_2);
@if (($difference_brightness < $brightness_min) or ($difference_color < $difference_min))
{
@return false;
}
@else
{
@return true;
}
}
@function color_brightness_AERT($color)
{
$r: red($color);
$g: green($color);
$b: blue($color);
@return (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
}
@function color_difference_AERT($color1, $color2)
{
$r1: red($color1);
$g1: green($color1);
$b1: blue($color1);
$r2: red($color2);
$g2: green($color2);
$b2: blue($color2);
@return (math_max($r1, $r2) - math_min($r1, $r2)) + (math_max($g1, $g2) - math_min($g1, $g2)) + (math_max($b1, $b2) - math_min($b1, $b2));
}
// -- math stuff ----------------------------------------------------------
@function math_max($value1, $value2)
{
@if ($value1 > $value2) { @return $value1; }
@return $value2;
}
@function math_min($value1, $value2)
{
@if ($value1 < $value2) { @return $value1; }
@return $value2;
}
@function math_absolute($value)
{
@if ($value > 0) { @return $value; }
@return $value * -1;
}
@haschek
Copy link
Author

haschek commented Jul 4, 2011

Hi Aaron, thx for the link. The compass methods are not really similar, because they only give you back a predefined light/dark color, based on the threshold. So it depends only on your inputs if the color combination has sufficient contrast to be accessible, even for visually impaired people.

Just take the the compass example [1], #333333 text on #C82523 background. This combination got a brightness difference of 35, and a color difference of 179 [2]. To be accessible, the brightness difference should be near 125 or above, the color difference around 500 and above (some guidelines say 400). Just use the example combination and transform it to grayscale, you will see it ...

So, what will the methods above do:

color_adjust_contrast_AERT(#333333, #C82523) => black
=> DEBUG: #333333 was adjusted to black but this is not enough contrast to #c82523 (AERT).
color_adjust_contrast_AERT(#C82523, #333333) => #e8d5d4

This helps to get color combinations what are really accessible/readable, for most people. With the test methods, you even can chain that methods, this example is the short one without any conditionals:

color_adjust_contrast_AERT(#C82523, color_adjust_contrast_AERT(#333333, #C82523)) => #d6908f

So, an inaccessible input of #333/#C82523 will lead to two accessible results: #333/#e8d5d4 and black/#d6908f Try it yourself [2], and yes, the colors really look different now, but in this case it's a feature :)

cheers,
Haschek

[1] http://compass-style.org/examples/compass/utilities/contrast/
[2] http://www.snook.ca/technical/colour_contrast/colour.html

@aaronrussell
Copy link

Hi @haschek thanks for clarifying. I commented a bit hastily before appreciating the true nature of your mixin :)

@tony
Copy link

tony commented Aug 28, 2017

I don't advise using this code unless it's licensed MIT/Apache/BSD/ISC/etc. LGPL/GPL isn't clear about how it handles scripting languages and copy-pasting, so there are various hidden traps (albeit likely not intended by the author) that could create a derivative if you use the code.

@ermarkar
Copy link

ermarkar commented Mar 26, 2018

I guess there is an issue with the function. I have passed both the colors white

$i: color_adjust_contrast_AERT(white,white);
.b{
  color:$i;
}

and this returned the white color?

@Merovex
Copy link

Merovex commented Apr 25, 2018

I found that I was able to establish a 4.5:1 contrast ratio (WCAG 2.0-AA Normal Text, WCAG 2.0-AAA Large Text) by setting the $difference_min value to 320. I was able to establish almost 7:1 (WCAG 2.0-AAA Normal Text) by setting $difference_min value around 355. The actual value in the second case would hover between 6.7:1 and 7:1 depending on the background color. As you approach 400, the color washes out. Since my site's base-font is in the Large Text range, I settled on a score of 340.

I left the $brightness_min value stock.

To test this, I used WebAIM's color contrast tool. For background colors, I used the Bootstrap 4.1 alert SCSS, using a bootswap gunmetal swatch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment