Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessible Routing #433

Closed
wants to merge 11 commits into from
Closed

Conversation

MelSumner
Copy link
Contributor

@MelSumner MelSumner commented Jan 18, 2019

Rendered

Updated on Jan 12, 2020 to reflect conversations during framework f2f weekend.

RFC to help routes be accessible in Ember
@MelSumner MelSumner changed the title Create 0000-a11y-routing.md Accessible Routing Jan 18, 2019
Copy link

@Robdel12 Robdel12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am so so so so so so so so excited for this


## Alternatives
1. We could try to implement something like Apple’s first responder pattern: https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder
2. Wait for a SPA solution to be implemented in assistive technology. I would note that this option seems less than ideal, since this specific issue has been reported to the different assistive technologies available, and has been an on-going issue for many years now.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, and I bet AT vendors will try and push the burden onto the browsers since AT operates at the OS level.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1


## How we teach this

For phase one, the guides should include information about skip links, since the application author will need to add those in themselves (just as they would for a static site). For phase two, we would explain that skip links are no longer needed because we have integrated that functionality in a more machine intelligent way.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For phase two, we would explain that skip links are no longer needed because we have integrated that functionality in a more machine intelligent way.

Is this true? I usually still keep nav skip links in my SPAs because it's helpful to move past the giant headers / nav bars web apps have.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Robdel12 the idea for phase 2 is that we'd make it so AT would automatically implement skip link functionality so it was a cohesive experience- I think of it as an upgrade to skip links.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skip links are also readily utilized by keyboard-only users, so it would still be a concern outside of ATs. Additionally, as an author, I would expect a means of disabling the generated skip link in case where we want to implement something much more custom and robust.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, AT is a red herring as far as skip-links are concerned. The main group who rely on skip links are keyboard users without AT. Screen reader users have a variety of alternative navigation mechanisms available.

Skip-links will still be necessary, until browsers provide at least one built-in mechanism for reaching the main content (e.g. heading navigation, landmark navigation, or spatial navigation).

So we have a puzzle to figure out: why don't screen readers read out the new content or appropriately move focus when the user navigates to a new route within an Ember application. In researching the issue more thoroughly, we discovered that NVDA doesn't seem to have a way to handle the history API. (Which makes sense- screen readers were released before the history API was released.)

One of the challenges is that screen reader technology is all closed-source except for [NVDA](https://www.nvaccess.org/), the open source screen reader for Windows (and typically used with Firefox). The source code for NVDA is available on [GitHub](https://github.com/nvaccess/nvda/) and is written in Python. It seems useful, at least initially, to try to solve for SPAs + NVDA, since both can be done in open source. (Related: [NVDA Issue #6606](https://github.com/nvaccess/nvda/issues/6606))

Copy link

@Robdel12 Robdel12 Jan 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've long had a dream to take NVDA (since it's OSS) and turn make a "headless" screen reader version of it. Just like we have a headless Chrome we push around in tests, I'd like a headless AT to send commands to and asset the right thing has happened. Like "press enter on this link, now assert the spoken output of the AT is correct & it focuses the correct element in the DOM"

It's a neat idea, but I have no idea if its even possible.

@locks locks added the T-framework RFCs that impact the ember.js library label Jan 19, 2019
@sandstrom
Copy link
Contributor

sandstrom commented Jan 24, 2019

This is probably an unpopular point of view, but I think there are other areas that should be prioritized over accessibility. Here are three examples:

  1. Any of the many things that the community is already eagerly waiting for.

  2. Better localization support. There are ~6.5 billion non-native english speakers, and better support for handling multiple languages, currency formatting, etc. would be better in terms of "accessibility" (billions of additional people that would "get access").

  3. Things that makes it easier to run Ember on low-cpu devices, such as phones (i.e. performance improvements). Again, billions of people don't have desktop computers with Intel chips. Would "enable access" for a lot more people than improved screenreader support will ever do.

Obviously one thing doesn't prohibit another, one can work on both screen reader support and localization functionality at the same time. But in a world of limited resources, I think efforts should be concentrated on other things.

text/0000-a11y-routing.md Outdated Show resolved Hide resolved
@MelSumner
Copy link
Contributor Author

This is probably an unpopular point of view, but I think there are other areas that should be prioritized over accessibility.

Thankfully, it's not a zero sum game! Since my primary area of expertise is accessibility, this is where I can propose specific improvements to Ember.

If your area of expertise is in any of the things you suggested, please do feel free to write RFCs to address those issues- there's room here for everyone! 👍

@Robdel12
Copy link

Robdel12 commented Jan 24, 2019

@sandstrom I have to wholly disagree with that statement based on one thing: every single new ember app out there right now is broken to AT. There's nothing the user can do to try and work around or fix it. I know it's not ideal, but if you need to translate something to a different language, you have the ability to. AT users are simply stuck high and dry. If they're an advanced AT user they might be able to figure out what's going on in a SPA, but probably won't bother.

I think every SPA framework / lib needs to take accessibility seriously & prioritize the work now that everyone is building SPAs. Until those framework / lib authors take it seriously, all SPAs will be broken to AT.

And, as @MelSumner said, it's not zero sum. You can get the ball rolling on first class i18n support if you'd like. Maybe learn some lessons from Angular when they took on that effort.

Lastly, this work might save everyone's companies from being sued for not being accessible.

@nathanhammond
Copy link
Member

This is roughly a duplicate of #66. Having already gone through the process I believe that Ember should be a thin core with extension points for the functionality that we need for accessibility which would then be provided by an addon. If (and only if) we can't implement things in user space we should figure out how to enable those things by way of changing core. Those changes should still be minimal and not focused on accessibility so much as exposing the internal Ember.js state required to accomplish the goal.

Benefits to being external:

  • Enables backwards compatibility. If you bundle something into Ember.js itself the user cannot get that functionality prior to that version of Ember. If distributed as an addon it can easily work all the way back to Ember 1.10, like ember-a11y.
  • Enables configuration by way of interactions between multiple addons and their build steps and ordering. This allows integration and coordination between multiple addons who may care about using a single extension point.
  • Enables shipping independently from Ember. This alone is a big enough reason for me to encourage never including accessibility-related functionality in core. We used jQuery in the early days of Ember to wallpaper over browser differences. AT is a massive target and not something consistent enough to address once and walk away from. For example: Refocus after load completes failing. ember-a11y/ember-a11y#72

My proposed path forward:

  • Implement the set of things that you would like to have default-enabled in Ember in one or more addons.
  • Once those addons are considered "done" open an RFC to have those be included in the default Ember blueprint.
  • Document their use in the Ember Guides.

This will be far less painful for maintenance over a longer time scale, allow us to better serve AT users, and enable more people to contribute to the success of the effort.

As a person who is incredibly familiar with this space (and the author of one of the referenced examples) I am strongly opposed to landing this RFC.


One of the challenges is that screen reader technology is all closed-source except for [NVDA](https://www.nvaccess.org/), the open source screen reader for Windows (and typically used with Firefox). The source code for NVDA is available on [GitHub](https://github.com/nvaccess/nvda/) and is written in Python. It seems useful, at least initially, to try to solve for SPAs + NVDA, since both can be done in open source. (Related: [NVDA Issue #6606](https://github.com/nvaccess/nvda/issues/6606))

## Detailed design
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This design should be explicit. As it stands this is not a concrete proposal; it cannot be implemented using the RFC as a guide.

2. When the user navigates to a new page in that website, the screen reader starts to read the content of that page
3. (repeat)

Perhaps we could achieve this by adding a function to set focus on the body element when a route transition has occurred.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't account for modals as well as non-body-hosted applications.

It's possible that enterprise Ember users have already implemented their own solution, since governments in many countries require this by law. An out of the box solution would need to be well-advertised and documented to avoid any potential conflicts for users.

## Alternatives
1. We could try to implement something like Apple’s first responder pattern: https://developer.apple.com/documentation/uikit/uiresponder/1621113-becomefirstresponder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to implement this, but it's a really wonky experience on the Web. To do it right you would likely need inert.

2. Wait for a SPA solution to be implemented in assistive technology. I would note that this option seems less than ideal, since this specific issue has been reported to the different assistive technologies available, and has been an on-going issue for many years now.

## Unresolved Questions
- how would we handle loading states?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ember-a11y works with loading states out of the box with exactly one caveat: when the loading state is below the pivot route.


## Unresolved Questions
- how would we handle loading states?
- how does/could/would this fit in with the router helpers work that Chad did?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely just an implementation detail and doesn't really need to be addressed in this RFC. This would clean up maybe a dozen lines of code in ember-a11y except that I want for ember-a11y to remain backwards compatible, so it won't adopt those until the old APIs are removed.

Given that, ideally any solution would not leverage those helpers unless it had to.

updated related references with a more complete list of accessibility APIs.
Lots of updates, technical solutions included.
updated original date
@MelSumner
Copy link
Contributor Author

@nathanhammond I believe I have attempted to address your concerns with the updates I have made to this RFC.

Update navigation message
@chadhietala
Copy link
Contributor

@MelSumner thanks for taking the time doing the investigation and providing paths forward. I think I would prefer that we do something with {{outlet}}s, either making an {{application-outlet}} or just making outlet smarter.

What I mean by making outlets smarter is that RFC#93 talked about exposing the {{get-route-info}} which is the data structure that knows what the logical hierarchy of the routes is. We would also like for this to be the data structure that is used for setting outlets as we have something that is similar today for setting the outlets. Another aspect this system is that we compute a from and to version of the RouteInfos, so technically we know the pivot point of the UI that we would need to focus.

I'm a bit cautious about exposing ember specific IDs that we use to bring focus to elements. The reason is that those IDs effectively become public API, but public API with no transition path if we decided to change the name or remove it. We have seen this with .ember-transitioning-in, .ember-view etc. Instead I think we could use a weakmap to look up the element that needs focus().

Overall though I think this is heading in the right direction.


Perhaps we could achieve this by adding a function to set focus on the body element when a route transition has occurred.
- A component is created that provides a screen-reader only message, informing the user that "Page transition is complete. You may now navigate as you wish." The text of this component could be internationalized.
- Focus is reset to the top left of the page
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of your description doesn't specify position by location, but instead by DOM hierarchy. This one slipped through.


Perhaps we could achieve this by adding a function to set focus on the body element when a route transition has occurred.
- A component is created that provides a screen-reader only message, informing the user that "Page transition is complete. You may now navigate as you wish." The text of this component could be internationalized.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that I've experimented with is an off screen "page change" log inside of an aria-live component. Every element in that log would provide information about what is happening, and the ability to jump to the section flagged by it. This has numerous problems:

  • It's a new paradigm.
  • It can't be done in a fully-automated way (the description for each page change area must be manually specified).
  • It doesn't support all AT use cases.


### Phase Two - Intelligent content focus
```hbs
<div tabindex="-1" class="ember-sr-only" id="nav-message">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • How should this handle partial renders? /foo/loading => /foo/bar/loading => /foo/bar/baz
  • How do you prevent this from being read at other times?
    • If you remove it, how do you prevent this from disappearing before AT has a chance to communicate it to the user?
    • If left in the page, stale information in this component could in theory be read during a transition to something new (especially if a large portion of content gets popped out of the page). This could make it seem as if you have landed on the previous state while the next state is still transitioning in.
  • How do you provide a human-understandable page name?
    • If it requires configuration, how do you ensure that users properly configure it?
  • Ember doesn't support localization out of the box, how do you localize the message?

this.router.on('routeDidChange', () => {
// we need to put this inside of something async so we can make sure it really happens **after everything else**
schedule('afterRender', this, function() {
document.body.querySelector('#ember-a11y-refocus-nav-message').focus();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Can we make this use a reference from inside of Ember?
  • How does this support Ember being used in a place where it is not in control of the entire application? The idea here seems to be to focus the "first" thing, similar to default AT behavior. However, if rootElement is set this doesn't play quite as nicely.

The addon would attempt to provide a sensible resolution for all involved:
- the performance gains from `pushState` remain in place
- users with assistive technology are informed that a page transition has occurred
- the focus is reset to the message itself, which also resets the focus of the page (as is desired for the screen-reader user)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This element should probably provide a jump link back to the section of the page that changed.


This allows the user with assistive technology to skip directly to the main content and is useful for not needing to repeatedly navigate through a navbar, for example.
```hbs
{{application-outlet}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I much prefer having a single outlet type. @ef4 and I have created a few over time:

  • liquid-outlet ... from liquid-fire.
  • focusing-outlet ... from ember-a11y which lifts the outlet stuff from liquid-fire.
  • animated-outlet ... in Apple Music which is a few dozen lines of code for hardcoded CSS fade transitions.

The problem with this strategy is that none of those solutions by default play nicely together: they're fighting over the same extension point. To wire them together you have to make one dependent upon another and wrap things. You end up (approximately) with:

{{! custom-outlet.hbs }}
{{#animated-outlet}}
  {{#focusing-outlet}}
    {{outlet}}
  {{/focusing-outlet}}
{{/animated-outlet}}

I feel like the API for the outlet itself should be extensible instead of needing to wrap it. I'm thinking something like element modifiers. It's a little hard to figure out what it should do since and outlet doesn't exactly have the same lifecycle hooks that a component has.


In this way, if an application author did not want to use the `{{application-outlet}}`, for whatever reason (maybe they are nesting apps, or maybe they already have a focus solution) they could remove the `application-` and be left with a classic outlet.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the behavior in this scenario should be opt out, not opt in. Start with outlet that does the accessible thing, opting out requires changing things in the application. Presumably the person who knows how to opt out will also be more familiar with what they're doing.

@wagenet
Copy link
Member

wagenet commented Jul 23, 2022

This still seems very important. I'm curious whether the routing spikes around Polaris would address this.

@wagenet wagenet added S-Proposed In the Proposed Stage T-routing labels Dec 2, 2022
@MelSumner
Copy link
Contributor Author

MelSumner commented Dec 2, 2022

👋
The Ember Framework Core team recognizes accessible routing as an integral part of the future router work that is now being designed and planned for the Polaris edition of Ember. We are also excited about the Navigation API work in progress that will lend itself to this work.

As such, I'm going to close this RFC.

For the time being, I encourage developers to check out ember-a11y-refocus, an addon that provides accessible routing for Ember apps.

@MelSumner MelSumner closed this Dec 2, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-Proposed In the Proposed Stage T-framework RFCs that impact the ember.js library T-routing
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants