Focus outside of visible area

3.2k views Asked by At

WCAG states that an element that has focus should always be visible to the user. This is especially hard, if not impossible in a chat window where space is limited.

When a keyboard or screen reader user tabs to the first button option and selects it, the content scrolls and button is no longer visible, breaking the "focus always visible" WCAG rule. Also if there is more than one button in the list the focus stays on the button and if they continue to tab, the window will scroll to where the focus is set. This is disorienting, and one can argue that when selecting a button the other options are not relevant, since new options are now available.

Example: https://recordit.co/2jDDvqg98J

One option is to stop scrolling when reaching the button so that the button is visible. But I feel that this is not a good experience and a compromise to comply with the WCAG rule. I have done some research and all conversational UI, with no exception, scroll to the bottom when new content is printed in the chat. If I deviate in the manner above to keep within WCAG I am breaking Jakobs Law.

Another option is to remove the focus from the selected button to the input field or the firs button in the next/new list of available buttons. But I feel that this will for blind users removes all points of reference.

Are there any other options or designs that you can think of to solve this in an accessible way?

2

There are 2 answers

0
Adam On BEST ANSWER

Your example is someway too complicated.

Let's take a standard HTML page : if you focus an element with the keyboard, you are still able to scroll the page using the mouse (without modifying the keyboard cursor), and make the element disapear from the viewport. This does not invalidate any WCAG criteria.

Pressing a char key on a textarea will make it visually focused again. For a button or a link, that would require tab then shifttab, to make the button visible and focused again.

Visual focus and keyboard focus are two distinct things, but if keyboard focus impacts the visual focus, that does not reciprocate

The problem you might have is not a problem with the focus, but more a problem with Time limits.

  1. Don't scroll if the user is not at the bottom of the window,

  2. Don't scroll if an action has been performed during last N seconds, or wait P seconds if no other action is performed before scrolling.

  3. Indicate that new messages have been received if the windows if not scrolling.

I think that Slack has a good implementation of the "do not scroll and indicate new messages" if you are not at the bottom.

0
GrahamTheDev On

Not a full answer

This is too large a question to answer in full, there are hundreds of things to consider and with just a GIF to see the behaviour I can only generalise.

Focus off screen

You have taken WCAG to the absolute letter and a little too far. The intention of this point is that when I change focus the focused item is visible on the screen.

If I scroll a web page you cannot expect focused items to stay on the page (otherwise no page in the world would be compliant if it was longer than a screen length!).

As long as when I focus the next item it scrolls into view and has a focus indicator I can see (correct contrast, not colour dependent) you are WCAG AAA rated on this point!

Auto scrolling content

This is where things get 'murky'. In your example content is added to a chat box and so the currently focused item scrolls off the screen.

Now if we move the focus to the latest options (buttons that appear) we have a problem that it may interrupt the flow of the screen reader and so they don't hear all the previous information and have to go back and listen again.

If we don't move the focus you have the problem you described where I press Tab (for example) and the page jumps back up before the content I just listened to.

Assuming we didn't have any other options this would still be preferable (not managing focus), but we do have options.

Time for some workarounds.

This is where accessibility gets fun, we need to find a few workarounds as there is no established pattern here.

I would argue that the biggest problem here is once the new text is spoken we jump back to the top of the page when we press Tab or other focus shortcuts.

One workaround for this would be to create a special div that gets focused the second i choose an option.

This div would have tabindex="-1" so it is not accessible via keyboard (only programatically).

The second I select an option we focus this div and then start inserting text.

That way when I press the <kbd>Tab</kbd> or shortcut for the next button it jumps to the first new option.

I have created a rudimentary fiddle below, it would need some improvement but gives you a pattern to test against / work with. (go full screen or you may not see the added buttons etc.)

var hasLoadedMore = 0;
$('.loadMore').on('click', function(e){
    if(hasLoadedMore == 0){ //just a horrible hack to simluate content only loading once when you click an option.
    console.log("option chosen");
    $('#focusAdjuster').attr('aria-hidden', false);
    $('#focusAdjuster').focus();
    console.log("focus adjusted");
    loadContent();
    }
});   



function loadContent(){
    ///ugly way of simulating the content being added dynamically.
    $('#chat').append('<p>additional text</p>');
    $('#chat').append('<p>more text</p>');
    setTimeout(function(){
    $('#chat').append('<p>more text</p>');
    $('#chat').append('<button>Option 1 new</button>');
    $('#chat').append('<button>Option 2 new</button>');
    }, 500);
    hasLoadedMore = 1;
}
.visually-hidden { 
    position: absolute !important;
    height: 1px; 
    width: 1px;
    overflow: hidden;
    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
    clip: rect(1px, 1px, 1px, 1px);
    white-space: nowrap; /* added line */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="chat">
<p>initial text</p>
<button class="loadMore">Option 1</button>
<button class="loadMore">Option 2</button>
<button class="loadMore">Option 3</button>
<div tabindex="-1" aria-hidden="true" id="focusAdjuster" class="visually-hidden">loading</div>
</div>

Explanation of fiddle

So they key points are the visually-hidden <div> and the accompanying line in the JavaScript that focuses that div before loading more content.

The div lets us change the focus after clicking a button. I also added 'loading' to the text inside that div to add an additional purpose as your application will be AJAX powered.

The div has tabindex="-1" so it cannot receive focus. I also added aria-hidden="true" to this and toggle this off when an option button is clicked, just before giving it focus.

I would toggle this back when focus leaves this div in the real world but I wasn't doing that in a quick demo fiddle.

Doesn't this 'fail' WCAG?

Yup! In this example I made a non-focusable item focusable and it has no action. I still think this is preferable to making it a <button> as that implies it has an action. It obviously isn't perfect.

However the key part of WCAG is the 'G' - they are Guidelines. The way I am suggesting is a 'hack' or a compromise based on the fact that I am realistic about development time and tech limitations.

The 'proper' way you can do the above without the div is with some careful focus management. With unlimited time and budget I would definitely do that.

But given that you don't have to only think about the Tab key with screen readers (you can navigate by links, buttons, headings, sections etc.) that becomes a nightmare when trying to intercept key strokes and so the above is the simplest way I could think to do this.

Alternative pattern.

Because the previous example 'fails' WCAG there is another option, however I would argue this is worse and introduces lots of problems.

Replace the text within the div with new text each time.

The down side is that you would need to provide 'previous item' buttons each time and track them, the plus side is this would be WCAG compliant (although I would argue not as usable even though you 'pass' the criteria.)

Also providing those 'previous' buttons introduces a number of problems with focus management again (when do we make them visible, do they get the focus, do they add more confusion?).

Think of it like a form wizard pattern, each set of questions is a 'step', so you can go back to previous steps, and you can only see the current step on screen.

I included this as this pattern could be expanded with some thought and to give the OP / other people some ideas, do NOT use it as it is

var hasLoadedMore = 0;
var optionChosen = "";
$('.loadMore').on('click', function(e){
    if(hasLoadedMore == 0){ //just a horrible hack to simluate content only loading once when you click an option.
    optionChosen = $(this).text();
    console.log("option chosen", optionChosen);
    loadContent();
    }
});   




function loadContent(){
    
    $('#chat').html('<button class="previousQuestion">You chose ' + optionChosen + '<span class="visually-hidden">(click here to go back and chose a different option)</span></button>');
    $('#chat').append('<h3>' + optionChosen + '</h3>');
    ///ugly way of simulating the content being added dynamically.
    $('#chat').append('<p>additional text</p>');
    $('#chat').append('<p>more text</p>');
    setTimeout(function(){
    $('#chat').append('<p>more text</p>');
    $('#chat').append('<button>Option 1 new</button>');
    $('#chat').append('<button>Option 2 new</button>');
    }, 500);
    hasLoadedMore = 1;
    
    
$('.previousQuestion').on('click', function(){
    console.log("now you would restore the previous question");
});   

    
}
.visually-hidden { 
    position: absolute !important;
    height: 1px; 
    width: 1px;
    overflow: hidden;
    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
    clip: rect(1px, 1px, 1px, 1px);
    white-space: nowrap; /* added line */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="chat">
<p>initial text</p>
<button class="loadMore">Option 1</button>
<button class="loadMore">Option 2</button>
<button class="loadMore">Option 3</button>
<div tabindex="-1" aria-hidden="true" id="focusAdjuster" class="visually-hidden">loading</div>
</div>