Hackernoon logoHow To Generate WCAG Compliant Contrast Color in Bootstrap by@itzsrikanth

How To Generate WCAG Compliant Contrast Color in Bootstrap

Author profile picture

@itzsrikanthSrikanth Sharma

Passionate DivOps Engineer

What is the importance of achieving the prescribed contrast ratio?

The intent of this Success Criterion is to provide enough contrast between text and its background so that it can be read by people with moderately low vision.
$theme-colors: (
  "primary":    $primary,
  "secondary":  $secondary,
  "success":    $success,
  "info":       $info,
  "warning":    $warning,
  "danger":     $danger,
  "light":      $light,
  "dark":       $dark
) !default;

Let us take example of a simple button. Based on the

$theme-colors
set, buttons are generated for each theme color variant in
scss/_button.scss
using
button-variant
mixin. This mixin receives a background color for which we need to generate corresponding contrast text color using
color-contrast
function. This function uses
$min-contrast-ratio
, which defaults to
4.5
to achieve WCAG 2 AA contrast ratio.

@each $color, $value in $theme-colors {
  .btn-#{$color} {
    @include button-variant($value, $value);
  }
}

In

color-contrast
function, the background color's
contrast-ratio
is compared with foreground colors in following order:

  • $color-contrast-light
  • $color-contrast-dark
  • $white
  • $black

We can override the values of

$color-contrast-light
and
$color-contrast-dark
which is set to default as
$white
and
$black
respectively. All these values are looped to find the suitable match.

@function color-contrast($background, $color-contrast-dark: $color-contrast-dark, $color-contrast-light: $color-contrast-light, $min-contrast-ratio: $min-contrast-ratio) {
  $foregrounds: $color-contrast-light, $color-contrast-dark, $white, $black;
  $max-ratio: 0;
  $max-ratio-color: null;

  @each $color in $foregrounds {
    $contrast-ratio: contrast-ratio($background, $color);
    @if $contrast-ratio > $min-contrast-ratio {
      @return $color;
    } @else if $contrast-ratio > $max-ratio {
      $max-ratio: $contrast-ratio;
      $max-ratio-color: $color;
    }
  }

  @warn "Found no color leading to #{$min-contrast-ratio}:1 contrast ratio against #{$background}...";

  @return $max-ratio-color;
}

When the value of contrast ratio is greater than

$min-contrast-ratio
, that color is returned. Contrast ratio can be calculated by finding out relative luminance of both background and foreground color in current loop.

@function contrast-ratio($background, $foreground: $color-contrast-light) {
  $l1: luminance($background);
  $l2: luminance(opaque($background, $foreground));

  @return if($l1 > $l2, ($l1 + .05) / ($l2 + .05), ($l2 + .05) / ($l1 + .05));
}

WCAG algorithm to calculate relative luminanceย is used, which replaces yiq contrast algorithm in Bootstrap.

relativeLuminance (c) {
  c = c / 255;
  return c < 0.03928 ? c / 12.92 : 
  Math.pow((c + 0.055) / 1.055, 2.4);
}

SCSS implementation:

@function luminance($color) {
  $rgb: (
    "r": red($color),
    "g": green($color),
    "b": blue($color)
  );

  @each $name, $value in $rgb {
    $value: if($value / 255 < .03928, $value / 255 / 12.92, nth($_luminance-list, $value + 1));
    $rgb: map-merge($rgb, ($name: $value));
  }

  @return (map-get($rgb, "r") * .2126) + (map-get($rgb, "g") * .7152) + (map-get($rgb, "b") * .0722);
}

Here,

$_luminance-list
is a list of all possible value for
Math.pow((c + 0.055) / 1.055, 2.4)
where
c
lies between [0, 255], which is the range for any channel. This method is used to overcome the difficulty of not having a power function in SCSS as mentioned here. The same list is maintained in Bootstrap as well, with 4th decimal point. Refer here for more precise value. This

The

red()
,
green()
and
blue()
are SCSS in-built functions to extract each channel's value for calculation.

opaque
is function, to remove the alpha channel by truncating it and mixing it with white in a ratio equal to alpha channel's value.

@function opaque($background, $foreground) {
  @return mix(rgba($foreground, 1), $background, opacity($foreground) * 100);
}

Few PRs are already resolved to achieve this contrast ratio like mentioned here and here.

References

  1. https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.htm
  2. https://github.com/twbs/bootstrap/pull/30168

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.