You can’t read this sentence - A11y automation

A presentation at JSFest in April 2019 in Kyiv, Ukraine, 02000 by Mauricio Palma

Slide 1

Slide 1

Slide 2

Slide 2

15 years ago…

Slide 3

Slide 3

Slide 4

Slide 4

Slide 5

Slide 5

7 years ago…

Slide 6

Slide 6

2012

Slide 7

Slide 7

Slide 8

Slide 8

Slide 9

Slide 9

Slide 10

Slide 10

She had a long history…

Slide 11

Slide 11

The grandma

Slide 12

Slide 12

2012 From

Slide 13

Slide 13

To

Slide 14

Slide 14

They founded their label and…

Slide 15

Slide 15

The Man

Slide 16

Slide 16

Slide 17

Slide 17

Slide 18

Slide 18

Today

Slide 19

Slide 19

Mauricio Palma Co-founder and all kind of things @WDLK Product Engineer @sinnerschrader everywhere else @palmaswell

Slide 20

Slide 20

But

Slide 21

Slide 21

Why?

Slide 22

Slide 22

Back in The Day

Slide 23

Slide 23

Way harder!

Slide 24

Slide 24

opportunity

Slide 25

Slide 25

Nature of the web

Slide 26

Slide 26

We use our our abilities

Slide 27

Slide 27

Slide 28

Slide 28

You can’t read this sentence A11y automation April, 2019

Slide 29

Slide 29

Slide 30

Slide 30

That was awkward! Right?

Slide 31

Slide 31

Slide 32

Slide 32

Literally hurts!

Slide 33

Slide 33

217 million

Slide 34

Slide 34

How to fix the numbers?

Slide 35

Slide 35

Blurry Vision

Slide 36

Slide 36

Slide 37

Slide 37

Key Takeaways Publish all information and content from your site in your HTML Use semantic HTML and the appropriate ARIA landmarks Write screen reader friendly markup Provide keyboard navigation

Slide 38

Slide 38

Partial vision loss

Slide 39

Slide 39

Slide 40

Slide 40

Key Takeaways Besides the mentioned semantic HTML and screen reader friendly markup You should follow a linear logical layout, that supports at least 200% magnification

Slide 41

Slide 41

Central vision loss

Slide 42

Slide 42

Slide 43

Slide 43

Key Takeaways Always put form elements in context. Meaning putting buttons, inputs, and notifications together in a logical manner For action elements in your interface use a good combination of colors, shapes, and text Do not rely on colors to convey meaning

Slide 44

Slide 44

Dyslexia

Slide 45

Slide 45

Slide 46

Slide 46

Key Takeaways Align your text to the left and keep a consistent layout Keep content short, clear and simple Consider producing materials in other formats (for example audio or video) Let the users change the contrast between background and text

Slide 47

Slide 47

Colorblindness

Slide 48

Slide 48

Slide 49

Slide 49

Slide 50

Slide 50

Key Takeaways Make sure that colors are not your only method of conveying important information.

Slide 51

Slide 51

Sunlight Bad lightning Temporary disabilities

Slide 52

Slide 52

Slide 53

Slide 53

Persona spectrum Permanent Temporary Situational

Slide 54

Slide 54

more technology toWe include people use

Slide 55

Slide 55

A11y everywhere

Slide 56

Slide 56

You Your code Code Unit Test tests Ship

Slide 57

Slide 57

Friday night deployment?!

Slide 58

Slide 58

Color Contrast Automation

Slide 59

Slide 59

AA || AAA Aa Aa Aa Aa Aa

Slide 60

Slide 60

O(n^2)

Slide 61

Slide 61

2500

Slide 62

Slide 62

Forward A11y color contrast automation github.com/palmaswell/forward

Slide 63

Slide 63

github.com/palmaswell/forward

Slide 64

Slide 64

Guideline 1.4 Distinguishable: Make it easier for users to see and hear content including separating foreground from background.

Slide 65

Slide 65

AA >= 4.5:1

Slide 66

Slide 66

AAA >= 7:1

Slide 67

Slide 67

Large-scale text AA >= 3:1

Slide 68

Slide 68

Large-scale text AAA >= 4.5:1

Slide 69

Slide 69

Large-scale text 24px || 19px bold

Slide 70

Slide 70

Other exceptions Logos and brand

Slide 71

Slide 71

Other exceptions Inactive UI elements

Slide 72

Slide 72

Color contrast Can your read this? 1.6 ratio

Slide 73

Slide 73

Color contrast Can your read this? 8.05 ratio AAA

Slide 74

Slide 74

WCAG definition of relative luminance Note 1: For the sRGB colorspace, the relative luminance of a color is defined as L = 0.2126 * R + 0.7152 * G + 0.0722 * B where R, G and B are defined as: if RsRGB <= 0.03928 then R = RsRGB/12.92 else R = ((RsRGB+0.055)/1.055) ^ 2.4 if GsRGB <= 0.03928 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4 if BsRGB <= 0.03928 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4

Slide 75

Slide 75

Excuse me?

Slide 76

Slide 76

Luminance

Slide 77

Slide 77

Relative Luminance

Slide 78

Slide 78

0

Slide 79

Slide 79

1

Slide 80

Slide 80

WCAG definition of relative luminance Note 1: For the sRGB color space, the relative luminance of a color is defined as L = 0.2126 * R + 0.7152 * G + 0.0722 * B where R, G and B are defined as:

Slide 81

Slide 81

sRGB standardized by the International Electrotechnical Commission

Slide 82

Slide 82

sRGB

Slide 83

Slide 83

Our visual system

Slide 84

Slide 84

Web Browsers Default color space Visual System Works in a similar way

Slide 85

Slide 85

Chromaticity | Chromaticity | Red | Green | Blue | White point | |———————|————-|—————-|—————|———————| | x | 0.6400 | 0.3000 | 0.1500 | 0.3127 | | y | 0.3300 | 0.6000 | 0.0600 | 0.3290 | | Y | 0.2126 | 0.7152 | 0.0722 | 1.0000 |

Slide 86

Slide 86

Step 1 xyY color space Y values represent the luminance of a color entry export enum YValues { r = 0.2126, g = 0.7152, b = 0.0722, }

Slide 87

Slide 87

Step 2 Color contrast export function calculateRGBEntry( entry: number, y: Type.YValues): number { const average = entry / 255; }; return average <= 0.03928 ? average / 12.92 * y : ((average + 0.055) / 1.055 ) ** 2.4 * y;

Slide 88

Slide 88

Step 2 Color contrast export function luminance( sRGB: Type.RGB ): number { return calculateRGBEntry(sRGB[0], Type.YValues.r) + calculateRGBEntry(sRGB[1], Type.YValues.g) + calculateRGBEntry(sRGB[2], Type.YValues.b); }

Slide 89

Slide 89

Step 2 Color contrast export function contrastRatio( sRGB: Type.RGB, sRGB2: Type.RGB): number { const light = Math.max(luminance(sRGB), luminance(sRGB2)); const dark = Math.min(luminance(sRGB), luminance(sRGB2)); } return +((light + 0.05) / (dark + 0.05)).toFixed(2);

Slide 90

Slide 90

N colors [ Aa Aa Aa Aa ]

Slide 91

Slide 91

A11y everywhere

Slide 92

Slide 92

Quicksort fast sorting algorithm

Slide 93

Slide 93

Step 3 Sort export function quickSort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number, lo: number, hi: number ): void { if (lo < hi) { const pivot = cb(arr[Math.floor((lo + hi) / 2)].rgb); const p = partition(arr, lo, hi, pivot, cb); quickSort(arr, cb, lo, p.hi); quickSort(arr, cb, p.lo, hi); } } export function sort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number) { return quickSort(arr, cb, 0, arr.length - 1); }

Slide 94

Slide 94

Step 3 Sort export function quickSort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number, lo: number, hi: number ): void { if (lo < hi) { const pivot = cb(arr[Math.floor((lo + hi) / 2)].rgb); const p = partition(arr, lo, hi, pivot, cb); quickSort(arr, cb, lo, p.hi); quickSort(arr, cb, p.lo, hi); } } export function sort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number) { return quickSort(arr, cb, 0, arr.length - 1); }

Slide 95

Slide 95

Step 3 Sort export function quickSort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number, lo: number, hi: number ): void { if (lo < hi) { const pivot = cb(arr[Math.floor((lo + hi) / 2)].rgb); const p = partition(arr, lo, hi, pivot, cb); quickSort(arr, cb, lo, p.hi); quickSort(arr, cb, p.lo, hi); } } export function sort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number) { return quickSort(arr, cb, 0, arr.length - 1); }

Slide 96

Slide 96

Step 3 Sort export function partition( arr: Type.Color[], lo: number, hi: number, pivot: number, cb: (sRGB: Type.RGB) => number): { lo: number, hi: number} { if (lo <= hi) { if (cb(arr[lo].rgb) < pivot) { return partition(arr, lo + 1, hi, pivot, cb); } if (cb(arr[hi].rgb) > pivot) { return partition(arr, lo, hi - 1, pivot, cb); } if (lo <= hi) { swap(arr, lo, hi); return partition(arr, lo + 1, hi - 1, pivot, cb); } } return { lo, hi }; }

Slide 97

Slide 97

Step 3 Sort export function quickSort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number, lo: number, hi: number ): void { if (lo < hi) { const pivot = cb(arr[Math.floor((lo + hi) / 2)].rgb); const p = partition(arr, lo, hi, pivot, cb); quickSort(arr, cb, lo, p.hi); quickSort(arr, cb, p.lo, hi); } } export function sort( arr: Type.Color[], cb: (sRGB: Type.RGB) => number) { return quickSort(arr, cb, 0, arr.length - 1); }

Slide 98

Slide 98

Step 3 Quicksort Aa Aa Aa Aa Aa

Slide 99

Slide 99

Step 3 Sorted by luminance Aa Aa Aa Aa Aa

Slide 100

Slide 100

Binary Search efficient algorithm that searches a sorted list

Slide 101

Slide 101

Step 4 Binary Search AAA color contrast check Match! Aa Aa Aa Aa Aa

Slide 102

Slide 102

Step 4 Search export function search( arr: Type.Color[], el: Type.Color, val: Type.A11yRatio, type?: Type.Search): number | [] { if (type === Type.Search.upper) { return upperSearch(arr, el, val, 0, arr.length); } return lowerSearch(arr, el, val, 0, arr.length); }; function lowerSearch<T extends QuickSearch>( arr: Array<T>, el: T, val: number, lo: number, hi: number): number | [] { const mid = Math.floor((lo + hi) / 2); const prev = mid - 1; const next = mid + 1; const midRatio = contrastRatio(arr[mid].rgb, el.rgb); if (lo < hi) { if (midRatio > val && prev >= 0) { if (contrastRatio(arr[prev].rgb, el.rgb) < val) { return mid; } return lowerSearch(arr, el, val, lo, mid); } if (midRatio < val && next < arr.length) { if (contrastRatio(arr[next].rgb, el.rgb) > val) { return next; } return lowerSearch(arr, el, val, mid, hi) } if (mid === val) { return mid; } } return []; };

Slide 103

Slide 103

Step 4 Search export function search( arr: Type.Color[], el: Type.Color, val: Type.A11yRatio, type?: Type.Search): number | [] { if (type === Type.Search.upper) { return upperSearch(arr, el, val, 0, arr.length); } return lowerSearch(arr, el, val, 0, arr.length); }; function lowerSearch<T extends QuickSearch>( arr: Array<T>, el: T, val: number, lo: number, hi: number): number | [] { const mid = Math.floor((lo + hi) / 2); const prev = mid - 1; const next = mid + 1; const midRatio = contrastRatio(arr[mid].rgb, el.rgb); if (lo < hi) { if (midRatio > val && prev >= 0) { if (contrastRatio(arr[prev].rgb, el.rgb) < val) { return mid; } return lowerSearch(arr, el, val, lo, mid); } if (midRatio < val && next < arr.length) { if (contrastRatio(arr[next].rgb, el.rgb) > val) { return next; } return lowerSearch(arr, el, val, mid, hi) } if (mid === val) { return mid; } } return []; };

Slide 104

Slide 104

Step 4 Search export function search( arr: Type.Color[], el: Type.Color, val: Type.A11yRatio, type?: Type.Search): number | [] { if (type === Type.Search.upper) { return upperSearch(arr, el, val, 0, arr.length); } return lowerSearch(arr, el, val, 0, arr.length); }; function lowerSearch<T extends QuickSearch>( arr: Array<T>, el: T, val: number, lo: number, hi: number): number | [] { const mid = Math.floor((lo + hi) / 2); const prev = mid - 1; const next = mid + 1; const midRatio = contrastRatio(arr[mid].rgb, el.rgb); if (lo < hi) { if (midRatio > val && prev >= 0) { if (contrastRatio(arr[prev].rgb, el.rgb) < val) { return mid; } return lowerSearch(arr, el, val, lo, mid); } if (midRatio < val && next < arr.length) { if (contrastRatio(arr[next].rgb, el.rgb) > val) { return next; } return lowerSearch(arr, el, val, mid, hi) } if (mid === val) { return mid; } } return []; };

Slide 105

Slide 105

Enhanced Color

Slide 106

Slide 106

Step 5 Enhanced colors export function createEnhanced( rawColor: Type.Color, aaa: Type.Color[] | [], aa: Type.Color[] | [] ): Type.colorEnhanced { const rgb = rawColor.rgb; return { name: rawColor.name, rgb, aaa, aa, toRGB(): string { return toRGBString(rgb); }, toHEX(): string { return tinyColor(toRGBString(rgb)).toHexString(); }, toHSL(): string { return tinyColor(toRGBString(rgb)).toHslString(); }, toRGBA(alpha: number): string { const color = tinyColor(toRGBString(rgb)); color.setAlpha(alpha); return color.toRgbString() } } }

Slide 107

Slide 107

Step 5 Enhanced colors export function createEnhanced( rawColor: Type.Color, aaa: Type.Color[] | [], aa: Type.Color[] | [] ): Type.colorEnhanced { const rgb = rawColor.rgb; return { name: rawColor.name, rgb, aaa, aa, toRGB(): string { return toRGBString(rgb); }, toHEX(): string { return tinyColor(toRGBString(rgb)).toHexString(); }, toHSL(): string { return tinyColor(toRGBString(rgb)).toHslString(); }, toRGBA(alpha: number): string { const color = tinyColor(toRGBString(rgb)); color.setAlpha(alpha); return color.toRgbString() } } }

Slide 108

Slide 108

Colors Under Control

Slide 109

Slide 109

Demo

Slide 110

Slide 110

It’s open Use it, play with it, make it better! github.com/palmaswell/forward

Slide 111

Slide 111

Benefits New opportunities Offer utility and elegance Profitable Legal compliance

Slide 112

Slide 112

A11y costs factors Before: 10% (imaginary number) Middle: x100% After: x1000%

Slide 113

Slide 113

Questions?

Slide 114

Slide 114

Slide 115

Slide 115

Thanks! Github: @palmaswell Twitter: @palmaswell mauricio.palma@sinnerschrader.com