Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Building for Accessibility

5,237 views

Published on

The advent of JavaScript applications means that we need to make sure we evolve our thinking on how to develop accessible experiences on top of the web platform. Just as we no longer design static visual experiences and instead plan for motion in our applications, we also need to spend the same intentionality designing and building experiences for users of assistive technology.

You'll leave this presentation with a new approach to thinking about building web applications.

(Originally presented at Wicked Good Ember, June 16, 2015.)

Published in: Technology
  • Be the first to comment

Building for Accessibility

  1. 1. Designing
  2. 2. Is This Designing? http://www.adobe.com/inspire/2013/11/photoshop-reflow-generator.html
  3. 3. Is This Designing? https://flic.kr/p/qR7Lo8
  4. 4. Is This Designing? http://thisoldcity.com/advocacy/photos-what-snow-tells-us-about-creating-better-public-spaces-e-passyunk-avenue
  5. 5. Is This Designing? https://flic.kr/p/7bRxB2
  6. 6. Yes.
  7. 7. @nathanhammond nathanhammond.com nathan@nathanhammond.com nathanhammond
  8. 8. http://www.unicode.org/reports/tr51/tr51-2.html#Emoji_Modifiers
  9. 9. Good Design https://flic.kr/p/die16e, https://flic.kr/p/99ad9q
  10. 10. Universal Design Stokke Tripp Trapp
  11. 11. Universal?
  12. 12. Designing for Accessibility
  13. 13. http://www.istockphoto.com/photo/group-of-business-men-and-women-in-a-meeting-37790018
  14. 14. https://flic.kr/p/asasy3https://flic.kr/p/asasy3
  15. 15. https://commons.wikimedia.org/wiki/File:US_House_Committee.jpg
  16. 16. https://flic.kr/p/9ZA2Jx
  17. 17. Demo
  18. 18. Learnings • Screen readers start reading from the top of the page on each page load. • Dynamically updating the DOM results in no information being passed to the user by default. • Markup is still important. Ember's real links and the code you write in your templates is important. • ARIA roles still do what we expect.
  19. 19. Building for Accessibility
  20. 20. Page Loading We want to emulate the page load behavior demonstrated with static pages.
  21. 21. End-User Goals • Have the screen reader automatically start reading the content of the page. • Have it start reading the content from the best point in the application hierarchy. • Make it clear to the user that the content on the page has changed. • Follow other recommended patterns that screen reader users are familiar with.
  22. 22. Ember Goals • Have the solution work with current idiomatic Ember code or minimize migration cost. • Make the change in a backwards compatible way to meet Ember's stability without stagnation goal. • The default approach should be accessible, API design is a feature. • Provide for extensibility with hooks enabling enhancement of this higher-level feature.
  23. 23. Exploration
  24. 24. Inventory Router, Transitions, Routes, Templates, Outlets, HTMLBars
  25. 25. Outlet Focusing What if we set the focus on the route's outlet immediately after the transition completes?
  26. 26. Research
  27. 27. Screen Readers • It's like the Internet Explorer and Netscape HTML/ CSS/JS compatibility workarounds all over again. • Screen readers tend to read whatever you focus. • Nobody likes being dumped into forms mode. • Screen readers are aware of content on the page changing, but don't present that to the user until it is asked for.
  28. 28. Ember • The top level outlet is generated and wrapped in a containing DIV. • All other outlets are populated by their templates without first being wrapped in a containing DIV. • Routes have no reference to their rendered output. • You can have multiple outlets in a single route. • No-op transitions trigger no route hooks.
  29. 29. Solution Design
  30. 30. API Design It'd be nice if every route had a `focus` hook that was called with the outlet contents.
  31. 31. Ember.Route.reopen({! focus(morph) {! var elem = morph.firstNode;! ! try {! if (!elem.getAttribute('tabindex')) {! if (isInteractive(elem)) {! elem.setAttribute('tabindex', 0);! } else {! elem.setAttribute('tabindex', -1);! }! }! ! elem.focus();! } catch (e) {}! }! });
  32. 32. Development How do we call the `focus` hook?
  33. 33. Ember.Route.reopen({! enter(transition) {! var route = this;! ! // Focus "up one level" for index routes.! if (! transition.pivotHandler &&! this.routeName === transition.pH.routeName + '.index'! ) {! transition.pivot = transition.pivotHandler.routeName;! route = transition.pivotHandler;! this._focus(route, transition);! }! ! // Handle fresh entries.! if (!transition.pivot) {! transition.pivot = this.routeName;! this._focus(route, transition)! }! ! return this._super(...arguments);! }! });
  34. 34. Ember.Route.reopen({! _focus(route, transition, parent) {! var focus = new Ember.RSVP.Promise(function(resolve, reject) {! this.focusPromiseResolve = resolve;! }.bind(route));! ! // Set up our context for after the transition completes.! var transitionResolve = (function(transition, route) {! return function(result) {! delete transition.pivot;! ! Ember.run.scheduleOnce('afterRender', route, function() {! route.focus(result.focus)! });! };! })(transition, route);! ! // Clean up after ourselves in case there is a transition.retry() call.! var transitionReject = (function(transition, route) {! return function() {! delete transition.pivot;! };! })(transition, route);! ! Ember.RSVP.hash({ focus, transition })! .then(transitionResolve)! .catch(transitionReject);! }! });
  35. 35. Integration What happens when Ember needs to be modified.
  36. 36. diff --git a/packages/ember-htmlbars/lib/keywords/outlet.js b/packages/ember-htmlbars/lib/ keywords/outlet.js! index 317e899..6b01753 100644! --- a/packages/ember-htmlbars/lib/keywords/outlet.js! +++ b/packages/ember-htmlbars/lib/keywords/outlet.js! @@ -17,6 +17,10 @@ export default function(morph, env, scope, params, hash, template, inverse, visi! + if (env.outletState.main.render.focusPromiseResolve) {! + env.outletState.main.render.focusPromiseResolve(morph);! + delete env.outletState.main.render.focusPromiseResolve;! + }! keyword('@real_outlet', morph, env, scope, params, hash, template, inverse, visitor);! ! diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/ system/router.js! index bb157c1..5600f99 100644! --- a/packages/ember-routing/lib/system/router.js! +++ b/packages/ember-routing/lib/system/router.js! @@ -202,10 +202,18 @@ var EmberRouter = EmberObject.extend(Evented, {! for (var j = 0; j < connections.length; j++) {! + delete connections[j].focusPromiseResolve;! var appended = appendLiveRoute(liveRoutes, defaultParentState, connections[j]);! liveRoutes = appended.liveRoutes;! if (appended.ownState.render.name === route.routeName || appended.ownState.render.outlet === 'main') {! ownState = appended.ownState;! + // Pass forward the focus promise resolve hook from the route to the appropriate connection.! + if (route.focusPromiseResolve) {! + ownState.render.focusPromiseResolve = route.focusPromiseResolve;! + delete route.focusPromiseResolve;! + }
  37. 37. Contributing Back
  38. 38. Write an RFC! You've created and tested a feature! It's time to get it shipped!
  39. 39. http://bit.ly/rfc66
  40. 40. Usage Notes • You can use this today! http://bit.ly/outletfocusing • All template content must be wrapped in a `<div>`. Child outlets must also appear inside that `<div>`. • Automatically generated templates are just `{{outlet}}` and will thus have problems. • Provide feedback on RFC #66!
  41. 41. Demo
  42. 42. Learnings • It's a different experience from the static site. • We have programmatic control over the focusing behavior. • Setting the focus has additional consequences in terms of scrolling behavior.
  43. 43. Challenge

×