Accessibility: Signal changes to screen readers on navigation

This is split out from the master accessibility thread: Accessibility audit and shepherd for making improvements.

This is some feedback from an accessibility audit for one of our forum instances. The story is specific to that forum (eg, the post titles) but it applies to Discourse overall. I left it in the author’s original words to get the flavor of the feedback :slight_smile:

I started by clicking on the category "cookin on the job" since I like to eat!!

When you do this, the page does not refresh; it is modified on the fly. This is common practice now, however its drawback with respect to accessibility is that screen readers do not get a signal that content on the page has changed; the author is responsible to informing the user of this fact. The way this is done varies with the exact nature of what is being presented…

In this case, the two pieces of information the user must know are:

  • that something has actually happened (i.e. new information has indeed > appeared)

  • the nature of the new info (is this a message sequence, or merely another topic node)

If we click this category name, we indeed land on another topic node, listing the topics in this group (there is only one). So, the user would want to hear something like "1 topic found". This tells the user she has landed on a topic list, and how many items there are.

To implement this, place a div on the page (doesn’t matter where, but most likely at the end of the HTML), move it off-screen so sighted users will not see it, and mark it up as a live region, which tells the screen reader to speak all text changes within it without changing focus. FOr instance:

<div class="screen-reader-only message-area" aria-live="polite"> </div> 
.screen-reader-only { position: absolute; overflow: hidden; left: -99999; top: -99999; } 

This seems like it might be complicated, but as an initial idea perhaps there’s an Ember routing hook that would let us do something global like “on route change, add in one of these screen reader-only tags to at least tell the screen reader that the data on the page changed.” That wouldn’t get at the need for “tell them what changed,” which seems more complicated and specific to individual features.


We already have a hook on page change, maybe use that?

<script type="text/discourse-plugin" version="0.2">
    api.onPageChange((url, title) => {>{

I’ve seen multiple articles mention that focus management is important.

Would code like this suffice for notifying the screen reader that navigation has completed?

// entered a topic
if (post_number === 1) {
  ariaNotice("Entered topic"); // i18n of course
  // inline title, not header title
} else {
   ariaNotice("Entered at post {post_number} of {topic_title}"),
     post_number, topic.get('title'));
   // selects the <article> element
   document.getElementById('post_' + post_number).focus();

note: focused elements need tabindex=-1 for JS-only focusing, or tabindex=0 for user focusing.
It may be worthwhile to change our J / K navigation to use .focus() instead of CSS classes.

.topic-post {
  &.selected, &:focus-within {
    box-shadow: -3px 0 0 #e45735;
.topic-post article.boxed:focus {
    outline: none;

Screenshot of above, with <article tabindex=-1> focused:

I tried to keep the notice text short. ariaNotice means placing text into the live=polite section.

Also, note we already have a #offscreen-content div, so just reuse that.

As a catchall for other pages, you could probably just refocus #main-outlet.


Another problem: post controls have outline: 0 set, so you can’t notice when you’re focusing them.

I suggest duplicating the .d-hover CSS rules to cover :focus as well, it looks good.

Pressing space on the “show more” post action blurs, you need to hit Tab again to reestablish focus.

Continued here: Accessibility: Focus management in topics

1 Like

@riking this is awesome, thanks for chiming in! :slight_smile:

It sounds like you have a bit more context on the pieces involved here than I do, but on first read it looks to me like the issue you’re bringing up is around focus management and navigation between topics (similar to Accessibility: Need way to browse between messages quickly - #3 by kevinrobinson and then you split that out to here: Accessibility: Focus management in topics - #3 by riking). And so I’ll leave those aside here and talk about those further over there. If I’m mixed up or missing something please chime in!

So for the original issue on this thread, I think we could try @sam’s awesome suggestion to use the onPageChange hook. I can see how it works here, and the only question is where to enable that hook. I think we’d want to have this be enabled by default (with opt-out possible) and not a separate plugin.

If I’m reading through the code correctly, it looks like a new initializer in here would be the right place to add in that call to api.onPageChange. For now we could just have it do something like:

   api.onPageChange((url, title) => {>{
           // insert the .screen-reader-only div if it isn't on the page already
           var screenReaderOnlyEl = document.getElementById('screen-reader-only-announcer');
           if (!screenReaderOnlyEl) {
             $('body').append('<div id="screen-reader-only-announcer" aria-live="polite"> </div>');
             screenReaderOnlyEl = document.getElementById('screen-reader-only-announcer');
           $(screenReaderOnlyEl).text('Page changed to: ' + title);

Does this seem like a reasonable first step?