SLIDE 1 @jessbudd4 #LaraconAU
Making Single Page Apps Accessible
SLIDE 2 @jessbudd4
🚷 📲
😮 🤖
SLIDE 4 @jessbudd4
SLIDE 6 Jess Budd
@jessbudd4 Front-end Developer & Accessibility Consultant
SLIDE 7 @jessbudd4
Are JavaScript frameworks bad for accessibility?
SLIDE 8 @jessbudd4
“
Nothing in React prevents us from building accessible web apps
- Leslie Cohn-Wein, Netlify
SLIDE 9 @jessbudd4
What is web accessibility?
SLIDE 10 @jessbudd4
Removing barriers that prevent people with disabilities using your website
SLIDE 11 @jessbudd4
1 in 5
Australians experience some form of disability
Source: www.and.org.au/pages/disability-statistics.html
SLIDE 12 @jessbudd4
357,000
Australians have a visual impairment
Source: www.and.org.au/pages/disability-statistics.html
SLIDE 13 @jessbudd4
1 in 6
Australians are affected by hearing loss
Source: www.and.org.au/pages/disability-statistics.html
SLIDE 14 @jessbudd4
Accessibility benefits everyone
SLIDE 15 @jessbudd4
SLIDE 16 Who is most impacted?
SLIDE 18 Screenreade r users
SLIDE 19 @jessbudd4
Semantic HTML
SLIDE 20 @jessbudd4
screenshot
SLIDE 21 // not very accessible card component <div class="product"> <div class=“name"> Product name </div> <div class="description"> Description of product </div> <div class="button">Add to cart</div> </div>
SLIDE 22
SLIDE 23 // more accessible card component <li> <h2> Product name </h2> <p> Description of product </p> <button>Add to cart</button> </li>
SLIDE 24 // more accessible card component <li> <h2> Product name </h2> <p> Description of product </p> <button>Add to cart</button> </li>
SLIDE 25 @jessbudd4
Divs are not buttons
SLIDE 26 <div tabindex="0" role="button"
- nKeyUp={keyUpHandler}
- nClick={clickHandler}
class="button"> Add to cart </div>
SLIDE 27 <div tabindex="0" role="button"
- nKeyUp={keyUpHandler}
- nClick={clickHandler}
class="button"> Add to cart </div>
SLIDE 28 <div tabindex="0" role="button"
- nKeyUp={keyUpHandler}
- nClick={clickHandler}
class="button"> Add to cart </div>
SLIDE 29 <div tabindex="0" role="button"
- nKeyUp={keyUpHandler}
- nClick={clickHandler}
class="button"> Add to cart </div> <button
Add to cart </button>
👎
SLIDE 30 @jessbudd4
Give buttons some ❤
SLIDE 31 @jessbudd4
Fragments for valid html
SLIDE 32 // not allowed render() { return ( <li>Hello</li> <li>World</li> ); }
SLIDE 33 // not allowed render() { return ( <li>Hello</li> <li>World</li> ); } // solution??? render() { return ( <div> <li>Hello</li> <li>World</li> </div> ); }
👏
SLIDE 34 // fragments syntax render() { return ( <React.fragment> <li>Hello</li> <li>World</li> </React.fragment> ); }
SLIDE 35 @jessbudd4
Vue Fragments
www.medium.com/the-vue-point/plans-for-the-next-iteration-of-vue-js-777ffea6fabf
Vue-fragment
SLIDE 36 @jessbudd4
Inputs & labels
SLIDE 37 // label not linked to input <label> Dog breed: </label> <input type="text" name="breed" />
SLIDE 38 // explicitly linked label to input <label for="uniqueId"> Dog breed: </label> <input id="uniqueId" type="text" name=”breed” />
SLIDE 39 // “for” becomes “htmlFor” in JSX <label htmlFor="uniqueId"> Dog breed: </label> <input id="uniqueId" type="text" name="breed" />
SLIDE 40 @jessbudd4
What if I can't set a unique ID in advance?
SLIDE 41 // implicitly linked label to input <label> Dog breed: <input type="text" name="breed"/> </label>
SLIDE 42 @jessbudd4
What if the design doesn’t have labels?
SLIDE 43 // label hidden with css is still announced <label class=“sr-only“ for=“dogBreed”> Dog breed: </label> <input id=“dogBreed” type="text" name=”breed" />
SLIDE 44 @jessbudd4
Hit Area
SLIDE 45 @jessbudd4
Page titles
SLIDE 47 // page title appears in browser tab <head> <meta charset="utf-8"> <title>Dogs Are The Best</title> <link rel="stylesheet" href="style.css"> </head>
SLIDE 48 // code executes when component mounts // same as the mounted hook in Vue componentDidMount() { document.title = ‘Heckin’ Good Doggo’; }
SLIDE 49 // update page title on routing componentDidMount() { document.title = ‘Heckin’ Good Doggo’; }
SLIDE 51 @jessbudd4
Visible focus styles
SLIDE 52 @jessbudd4
Safari: Firefox: Chrome:
Default browser styles
SLIDE 53 @jessbudd4
Meme where am I /lost
SLIDE 54 @jessbudd4
Screen shot of page with masses of links and question marks
???
SLIDE 55 /* don't just remove */ *:focus {
}
❌
SLIDE 56 /* don't just remove */ *:focus {
}
✅ ❌
/* replace with something! */ *:focus { /* branded focus styles here */ border: 2px dotted currentColor; }
SLIDE 57 /* extend hover styles */ .button:hover, .button:focus { border: 5px solid #33ffdb; }
SLIDE 58 @jessbudd4
Focus management
SLIDE 59 @jessbudd4
Single page applications use silent routing
SLIDE 60 @jessbudd4
Video of default browser behaviour
SLIDE 61 @jessbudd4
Move focus to new content
SLIDE 62 Tabindex Explained
tabindex=”0" // makes element focusable in tab/DOM order tabindex=”-1" // makes elements focusable by scripting only tabindex=”5” // Danger Will Robinson! // tabindex becomes tabIndex (camelCase) in JSX
SLIDE 63 @jessbudd4
Use React Refs
SLIDE 64 class PageHeading extends React.Component { constructer(props) { super(props); this.content = React.createRef(); // Create Ref } componentDidMount() { this.content.focus(); // Move focus to ref } render() { return ( <h1 tabindex="-1" ref={this.content}> // Reference Doggos, Puppers and Floofers </h1> ); } }
SLIDE 65 class PageHeading extends React.Component { constructer(props) { super(props); this.content = React.createRef(); // Create Ref } componentDidMount() { this.content.focus(); // Move focus to ref } render() { return ( <h1 tabindex="-1" ref={this.content}> // Reference Doggos, Puppers and Floofers </h1> ); } }
SLIDE 66 class PageHeading extends React.Component { constructer(props) { super(props); this.content = React.createRef(); // Create Ref } componentDidMount() { this.content.focus(); // Move focus to ref } render() { return ( <h1 tabindex="-1" ref={this.content}> // Reference Doggos, Puppers and Floofers </h1> ); } }
SLIDE 67 // update page title // and move users focus componentDidMount() { document.title = ‘Doggos be happy’;
this.content.focus();
}
SLIDE 68 @jessbudd4
Tooling & Testing
SLIDE 69 @jessbudd4
eslint-plugin-jsx-a11y
eslint-plugin-jsx-a11y eslint-plugin-vue-a11y
SLIDE 70 @jessbudd4
Axe-core
react-axe vue-axe
SLIDE 71 @jessbudd4
Google Lighthouse
https://developers.google.com/web/tools/lighthouse
SLIDE 72 @jessbudd4
Accessibility Insights
https://accessibilityinsights.io/
SLIDE 73 https://www.matuzo.at/blog/building-the-most-inaccessible-site-possible-with-a-perfect-lighthouse-score/
SLIDE 74 @jessbudd4
“
Automated testing is just the first step
SLIDE 75 @jessbudd4
Keyboard
SLIDE 76 @jessbudd4
Can I see where focus is? Does the tab order make sense? Can I access all required elements? Can I use advanced components? Does my focus move when needed?
What to look for
SLIDE 77 @jessbudd4
Screenreader
Voiceover (macOS) NVDA (Windows)
SLIDE 78 @jessbudd4
Takeaways
Use Semantic HTML Link form labels with inputs Update page titles on routing Manage keyboard focus Use tooling & test your apps
SLIDE 79 @jessbudd4
Go forth and make the web a better place
SLIDE 80 Thanks!
Slides at bit.ly/laracon19 @jessbudd4