├── README.md ├── focus-trap.md ├── img ├── linter.demo.png └── logger.demo.png ├── tab-focus.md └── tooling.md /README.md: -------------------------------------------------------------------------------- 1 | # Diving deep into A11y 2 | 3 | 1. Types of issues in accessing the web. 4 | 2. Assistive Tech (Screen reader, Switch, adaptive controller etc) 5 | 3. A11y trees - How browser understands 6 | 4. Role of semantics on A11y 7 | 5. Tab and Keyboard navigation 8 | 6. Focus trap in modal 9 | 7. Impact of using Unicode, and Icons, emojis. 10 | 8. Client-side routing Impact on A11y 11 | 9. Issues with infinite scroll 12 | 10. Tooling for A11y //code 13 | 14 | ## Code snippets 15 | 16 | 1. [Focus trapping in Dialog](focus-trap.md) 17 | 2. [Focus on keyboard navigation](tab-focus.md) 18 | 3. [Tooling for a11y](tooling.md) 19 | 20 | ## Misc 21 | 22 | 1. [Gist of different do's and don't and snippets](https://gist.github.com/vikas-parashar/74b690c775ab67a7ba2f324db7d53c15) 23 | 24 | 25 | ## Resources 26 | 27 | 28 | - [digitala11y.com](http://www.digitala11y.com) 29 | - Writing HTML with accessibility in mind 30 | [https://medium.com/alistapart/writing-html-with-accessibility-in-mind-a62026493412](https://medium.com/alistapart/writing-html-with-accessibility-in-mind-a62026493412) 31 | - Writing CSS with Accessibility in Mind 32 | [https://medium.com/@matuzo/writing-css-with-accessibility-in-mind-8514a0007939](https://medium.com/@matuzo/writing-css-with-accessibility-in-mind-8514a0007939) 33 | - Writing JavaScript with accessibility in mind - Manuel Matuzovic 34 | [https://medium.com/@matuzo/writing-javascript-with-accessibility-in-mind-a1f6a5f467b9](https://medium.com/@matuzo/writing-javascript-with-accessibility-in-mind-a1f6a5f467b9) 35 | - JavaScript is not an enemy of accessibility! – Marco's Accessibility Blog 36 | [https://marcozehe.wordpress.com/2016/11/23/javascript-not-enemy-accessibility/](https://marcozehe.wordpress.com/2016/11/23/javascript-not-enemy-accessibility/) 37 | - The Three Developers and the Insightful User Tester 38 | [https://www.24a11y.com/2017/three-developers-and-the-insightful-user-tester/](https://www.24a11y.com/2017/three-developers-and-the-insightful-user-tester/) 39 | - All about Web accessibility [https://link.medium.com/2HKNbecmf5](https://link.medium.com/2HKNbecmf5) 40 | - [https://link.medium.com/3QwcW2imf5](https://link.medium.com/3QwcW2imf5) 41 | - Deciding semantic html tag [http://html5doctor.com/downloads/h5d-sectioning-flowchart.png](http://html5doctor.com/downloads/h5d-sectioning-flowchart.png) 42 | 43 | Free course: 44 | 45 | - [https://classroom.udacity.com/courses/ud891](https://classroom.udacity.com/courses/ud891) 46 | 47 | Wiki 48 | 49 | - [https://en.wikipedia.org/wiki/Accessibility](https://en.wikipedia.org/wiki/Accessibility) 50 | 51 | Checklist 52 | 53 | - [https://www.wuhcag.com/wcag-checklist/](https://www.wuhcag.com/wcag-checklist/) 54 | 55 | Matzo blog 56 | 57 | - [https://www.matuzo.at/blog/building-the-most-inaccessible-site-possible-with-a-perfect-lighthouse-score/](https://www.matuzo.at/blog/building-the-most-inaccessible-site-possible-with-a-perfect-lighthouse-score/) 58 | 59 | Others 60 | 61 | - [https://a11y-style-guide.com/](https://a11y-style-guide.com/) 62 | - [https://a11yproject.com](https://a11yproject.com) 63 | - [https://developer.yoast.com/the-a11y-monthly-making-modals-accessible/](https://developer.yoast.com/the-a11y-monthly-making-modals-accessible/) 64 | - [https://css-tricks.com/a-css-approach-to-trap-focus-inside-of-an-element/](https://css-tricks.com/a-css-approach-to-trap-focus-inside-of-an-element/) 65 | - [https://github.com/The-Internals/Deep-dive-into-A11y/blob/master/tooling.md](https://github.com/The-Internals/Deep-dive-into-A11y/blob/master/tooling.md) 66 | - [https://patrickhlauke.github.io/aria/demos/modal/index-aria-inert.html](https://patrickhlauke.github.io/aria/demos/modal/index-aria-inert.html) 67 | -------------------------------------------------------------------------------- /focus-trap.md: -------------------------------------------------------------------------------- 1 | # Focus trapping in Dialog 2 | 3 | ```html 4 | 5 | ``` 6 | 7 | ```js 8 | onOpen = () => { 9 | keyShortcuts.bind(['tab'], this.onTab); 10 | } 11 | 12 | onTab(event) { 13 | if (!this.modalRef) { //or querySelector 14 | return; 15 | } 16 | 17 | const modal = this.modalRef; 18 | 19 | let firstFocusableElement = modal, 20 | lastFocusableElement = modal; 21 | 22 | // get all focusable elements inside modal 23 | const focusableElementsInDialog = this.modalRef.querySelectorAll( 24 | FOCUSABLE_ELEMENTS 25 | ); 26 | 27 | // get first and last focusable element 28 | if (focusableElementsInDialog && focusableElementsInDialog.length) { 29 | firstFocusableElement = focusableElementsInDialog[0]; 30 | lastFocusableElement = 31 | focusableElementsInDialog[focusableElementsInDialog.length - 1]; 32 | } 33 | 34 | // cycle on focusable elements within modal. 35 | let toFocusElement = null; 36 | if (event.shiftKey && document.activeElement === firstFocusableElement) { 37 | toFocusElement = lastFocusableElement; 38 | } else if (document.activeElement === lastFocusableElement) { 39 | toFocusElement = firstFocusableElement; 40 | } 41 | 42 | if (toFocusElement) { 43 | event.preventDefault(); 44 | toFocusElement.focus(); 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /img/linter.demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Internals/Deep-dive-into-A11y/dfb83997f1de3fe539b53b076fabe2def7ea93fa/img/linter.demo.png -------------------------------------------------------------------------------- /img/logger.demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Internals/Deep-dive-into-A11y/dfb83997f1de3fe539b53b076fabe2def7ea93fa/img/logger.demo.png -------------------------------------------------------------------------------- /tab-focus.md: -------------------------------------------------------------------------------- 1 | # Focus only on keyboard navigation 2 | On tab press add a class on top-level element informing the keyboard navigation is active and on mouse down remove the class. 3 | ```js 4 | componentDidMount() { 5 | //set state to `true` on TAB key press events 6 | keyShortcuts.bind(['tab'], () => { 7 | this.setState({ keyBoardActive: true }); 8 | }); 9 | 10 | //set state to `false` on mousedown events 11 | document.addEventListener('mousedown', () => { 12 | this.setState({ keyBoardActive: false }); 13 | }); 14 | 15 | 16 | 17 | //root level 18 |
19 | ... 20 | 21 | ``` 22 | 23 | ```css 24 | a:focus { 25 | outline: none; 26 | } 27 | .keyboard-active a:focus { 28 | outline: 3px solid blue; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /tooling.md: -------------------------------------------------------------------------------- 1 | # Tooling 2 | 3 | ## Linter [eslint-plugin-jsx-a11y](https://github.com/evcohen/eslint-plugin-jsx-a11y) 4 | 5 |  6 | 7 | ## Dev tool logger using [react-axe](https://github.com/dequelabs/react-axe) 8 | 9 | ```js 10 | //webpack config 11 | plugins: [ 12 | new webpack.DefinePlugin({ 13 | ... 14 | SHOW_A11Y_LOGGER: JSON.stringify( 15 | ENV !== 'production' && 16 | process.env.ENABLE_A11Y === '1' 17 | ), 18 | }), 19 | ... 20 | ], 21 | 22 | 23 | //index.js/root component 24 | componentDidUpdate(...) { 25 | if(SHOW_A11Y_LOGGER) { 26 | axe(React, ReactDOM, 1000); //react-axe 27 | } 28 | } 29 | 30 | //before 31 | > npm start 32 | 33 | //with logger 34 | > ENABLE_A11Y=1 npm start 35 | ``` 36 | 37 |  38 | 39 | ## Unit Testing 40 | 41 | ### Using [enzyme-chai-a11y](https://github.com/interviewstreet/enzyme-chai-a11y) 42 | 43 | ```js 44 | import auditA11y, { accessible } from "enzyme-chai-a11y"; 45 | chai.use(accessible); 46 | 47 | it("should not have accessibility violations", async () => { 48 | const results = await auditA11y(test
); 49 | expect(results).to.be.accessible(); 50 | }); 51 | ``` 52 | 53 | ### Using [axe-core](https://github.com/dequelabs/axe-core) 54 | 55 | ```js 56 | async function runAxe(node, config) { 57 | try { 58 | const results = await axeCore.run(node, config); //axe-core 59 | return results; 60 | } catch (error) { 61 | throw error; 62 | } 63 | } 64 | 65 | async function auditA11y(app, config = {}, enzymeConfig = {}) { 66 | const wrapper = mount(app, enzymeConfig); 67 | const node = wrapper.getDOMNode(); 68 | 69 | const results = await runAxe(node, config); 70 | 71 | wrapper.unmount(); 72 | return results; 73 | } 74 | 75 | function accessible(chai) { 76 | chai.Assertion.addMethod("accessible", function() { 77 | const { violations } = this._obj; 78 | const audit = new chai.Assertion(violations); 79 | const pass = violations.length === 0; 80 | 81 | audit.assert( 82 | pass, 83 | logVioloations(violations) //custom logger 84 | ); 85 | }); 86 | } 87 | 88 | chai.use(accessible); 89 | 90 | //use: 91 | it("should not violate any a11y rules", async () => { 92 | const results = await auditA11y(); 93 | expect(results).to.be.accessible(); 94 | }); 95 | ``` 96 | --------------------------------------------------------------------------------