22

Finding the Answers on Question page is not as easy as it could be when using a screen reader.

One of the most important tasks when using StackExchange sites, is reading the first few Answers. When using a screen reader, the best and most common way of navigating a page is typically to browse heading elements. Unfortunately, Answers have no headings associated with them, nor any other suitable markup, so it's pretty hard to eg. jump to the first or the second Answer.

I suggest adding an invisible-to-regular-users heading on top of each Answer, say <h6>Answer 1 (score 6, accepted answer)</h6>. The element should probably be right before the post-text div, so that moving on to the Answer text is easy (by advancing to the next element).

The choice of 6th level heading would make sense because some screen readers allow jumping directly to headings of given level, and there probably are no other <h6> elements. A semantically more correct choice would be <h3>, which would still be useful enough for screen reader navigation, as there appears to be only two such elements before Answers.

http://webaim.org/techniques/css/invisiblecontent/ has information about how to hide an element for sighted users but not for screen readers.

This suggestion is based on working hands-on with a blind programmer, who has experience with many screen readers and a braille display.

Edit: related discussion in https://meta.stackexchange.com/a/193262/267154

  • 1
    Does it help if there would be a userscript (tampermonkey/ghostscript) that adds that markup after pageload, until this gets implemented? Or does a screenreader gets confused by that? – rene Sep 7 '15 at 7:19
  • I'd need to test to find out if a userscript would work. Regardless, I'm suggesting this improvement mainly to improve the default experience of all visually impaired users. – Samuli Kärkkäinen Sep 8 '15 at 7:22
  • Tangentially related: meta.stackoverflow.com/q/343808 – Jason C Apr 20 '17 at 2:19
6

If a userscript can be used to alter the DOM and the screenreader picks up those changes the following script implements what you ask for:

(function (){
    var hide = true; // for testing set to false

    $('.answer').before(function(index) {
        var score = $(this).find('.vote-count-post').text(), 
            accepted = $(this).find('span.vote-accepted-on'),
            h6 = $('<h6>').html('Answer ' + 
                              (index + 1) + 
                              '(score ' + score + 
                              (accepted.length > 0?', accepted answer':'') + 
                              ')');

        // from http://webaim.org/techniques/css/invisiblecontent/
        if (hide) {
            h6.css('position','absolute')
            .css('left', '-10000px')
            .css('top', 'autor')
            .css('width', '1px')
            .css('height', '1px')
            .css('overflow', 'hidden');
        } 

        return h6;
    }); 
}());

For easy installation I published the script on Greasy Fork.

5
+100

I've added a client-side fix similar to (and inspired by) rene's script into the Stack Overflow Unofficial Patch (SOUP) v1.38.

Like rene's implementation, mine also uses <h6> elements and the same CSS hiding technique from webaim.org. However, instead of numbering the answers, my script labels them by their author, like this:

Answer by Author (score N)

This should hopefully be a bit more robust in case the answers get reordered or new answers are loaded via AJAX. It might be a little suboptimal on questions that have multiple answers by the same author, but that's fortunately rather uncommon.

Accepted, deleted and/or community wiki answers are also described as such in the heading; a particularly weird case might end up looking (well, sounding; they are invisible, after all) something like this:

Deleted community wiki answer by user123456 (score -9, accepted answer)

Since I already had code in SOUP for monitoring AJAX events, I also made the headings update in real time when the answer score or accept status changes, and to add new headings for new answers loaded via AJAX. This is mostly just a finishing touch, but I think it may be potentially useful if you happen to leave a question page open in a browser tab for a long time.

One thing I haven't tested is how all this actually works with a screen reader. Feedback from actual users of such software would be very much appeciated.


Ps. FWIW, here's the code:

var updateAnswerHeadings = function (where) {
    $(where).filter('.answer').add( $('.answer', where) ).each( function () {
        var answer = $(this);
        var signature = answer.find('.post-signature').eq(-1);
        var isWiki = signature.find('.community-wiki').length > 0;
        var author = signature.find('.user-details a[href^="/users/"]');

        var voteCount = answer.find('.vote-count-post');
        var score = Number( voteCount.text() );
        if ( voteCount.find('.vote-count-separator').length > 0 ) {
            var divs = voteCount.find('div'), up = divs.eq(0), down = divs.eq(-1);
            score = Math.abs( up.text() ) - Math.abs( down.text() );
        }
        var isAccepted = answer.find('.vote-accepted-on').length > 0;

        var text = ( isWiki ? 'Community wiki answer' : 'Answer' );
        if ( answer.hasClass('deleted-answer') ) text = 'Deleted ' + text.toLowerCase();
        if ( author.length > 0 ) text += ' by ' + author.text();
        text += ' (score ' + score + ( isAccepted ? ', accepted answer' : '' ) + ')';

        var heading = answer.find('.soup-answer-heading');
        if ( heading.length < 1 ) heading = $('<h6 class="soup-answer-heading">').prependTo(answer);
        heading.text(text);
    } );
};
SOUP.addContentFilter( updateAnswerHeadings, 'post' );
SOUP.subscribeToQuestion( function (data) {
    if ( /^(score|(un)?accept)$/.test( data.a ) ) setTimeout( function () {
        updateAnswerHeadings( '#answer-' + data.id );
    }, 10 );
} );

and the CSS to hide the headings:

.soup-answer-heading {
    overflow: hidden;
    height: 1px;
    width: 1px;
    position: absolute;
    left: -9999px;
}
  • 1
    I tested this on Windows' free NVDA, and OS X's built-in VoiceOver, both popular screen readers. The implementation works, but I have two improvement suggestions: 1) Change the text such that the score is mentioned before the author, which is less important information. 2) More importantly, move the voting buttons below the answer body, perhaps just above the comments. It's structurally good that the h6 is before anything else related to the answer, but currently a screen reader user first jumps into the h6, and must then browse through the buttons before reaching the answer body. – Samuli Kärkkäinen Oct 5 '15 at 8:59
  • 1
    @Samuli: Sorry for ignoring your comment for so long (and thanks to rene for drawing my attention back to this). I've tweaked the code in the development branch of SOUP to move the heading just before the answer body (but after the voting buttons), and to rephrase the heading text to e.g. "Answer (score n, accepted) by Author". I think that should address the issues you described. If you'd like to give the updated version a test, you can grab it here. – Ilmari Karonen Dec 12 '15 at 19:11
  • I tested the updated version, and it's very good now. Thanks! This userscript makes a big difference to usability for blind users. – Samuli Kärkkäinen Jan 1 '16 at 13:47
1

Another (or augmented) way to accomplish this is to treat answers as lists, making all answers (and comments) programmatically determinable by assistive technology. This is the approach taken by Wordpress. Each answer should be tagged as a <li> inside a <ul>, and comments on a particular answer would start a new list. It is arguably a WCAG 2.0 failure of 1.3.1 to not be doing this anyway since the list relationship is clear from the visual layout.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .