-
-
Save haschek/1059983 to your computer and use it in GitHub Desktop.
/* | |
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; | |
} |
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
Hi @haschek thanks for clarifying. I commented a bit hastily before appreciating the true nature of your mixin :)
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.
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?
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.
A similar method is already in Compass: http://compass-style.org/reference/compass/utilities/color/contrast/