I'm working on a modal component to be used across my organisation's website and digital services (around 17,000 pages created over the last 15+ years with millions of monthly users). As you can imagine these pages vary considerably and it's impossible to test them all.
To maximise the accessibility of this component I need to restrict focus (via the tab key and/or virtual cursor) to the modal while the modal is open.
My approach has been to attach a handler to the blur event on the last focusable element that returns focus to the first focusable element in the modal. This works absolutely fine unless the last focusable element in the modal is also the last focusable element on the page, in which case focus returns to the browser search bar.
I've been pretty puzzled by this for a couple of days and unable to find any information to indicate why this would be. Here's some simplified code which illustrates the issue:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Focus trap</title>
<style>button:focus {background-color: orange;}</style>
</head>
<body>
<div id="modal">
<button id="link-1">Link-1</button>
<button id="link-2">Link-2</button>
<button id="link-3">Link-3</button>
</div>
<script>
let last_link = document.getElementById('link-3'),
first_link = document.getElementById('link-1');
last_link.addEventListener('blur', () => {
first_link.focus();
})
</script>
</body>
</html>
I'm aware of a few things we could do, but none of these are ideal:
Try intercepting keyboard events rather than blur - but I'm reluctant to do that because it's likely to exclude users of some assistive technologies
Create and hide an additional focusable element in the modal - which I'm reluctant to do because it feels a bit of a hack
You can't go against the address bar or native toolbars being focused. They take the priority over whatever you could say and there are fortunately very good reasons for that to be so.
The solution is one of the two options you have already mentionned:
Intercept tab on the last element, as well as shift+tab on the first one
Put two hidden focusable elements, one at the beginning and one at the end, and move the focus to the first or last element as soon as your hidden focusable elements get focus.
I don't see which assistive technology would be defeated by doing tab/shift+tab interception. If you think about touch interface, anyway iOS/Android work completely differently with focus.
If you think about special devices, many of them simulate pressing keys on the keyboard, independently of the effective way their actions are triggered.
In case of doubt, the second one looks more like an hack, but is more robust if there effectively exist a special way to move the focus other than by the keyboard.
Related
When inserting new html into a section, I want the first tab press to skip the first few links and focus on a later link. Currently, my solution is to insert an empty <span> with tabindex='-1', and then to call focus() on it. However, I noticed that screen-reading software will jump to this focus(). Is there another way to set the initial tab focus, or to make screen-reading software ignore the focus()? (With NVDA, the div I'm inserting into is a role='status' which should be read whenever it updates, and the focus() grabs NVDA away about 40% of the time, without any seeming pattern.)
A method that failed is tabindex. Both Firefox and Chrome don't respect tabindex for the first tab press for inserted elements. In the code sample below, the second element has the last tabindex, and yet is focused first in Firefox. Behavior in Firefox differs between the first tab press and subsequent tab presses. Chrome ignores the tabindex completely for the first tab focus, so the first tab focus is always the first link.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script>
function k() {
document.getElementById("h").innerHTML = "<button tabindex='2' onclick='k()'>first</button><button tabindex='3' onclick='k()'>second</button><button tabindex='1' onclick='k()'>third</button>";
}
</script>
<div id="h"><button onclick='k()'>click me</button></div>
</body>
</html>
I also tried marking the empty <span> with aria-hidden='true' but that didn't help.
Attempt 1: From testing, if the DOM element that owns the existing focus is destroyed, the next object to receive a tab's focus is erratic, and behaves differently on Firefox and Chrome.
On Firefox, the focus is "ghostly shifted" to the first text-order element to appear, and then the first tab will shift forward and backward from this first element. So a tab will reach the element whose tab order is after the first element, and a shift tab will reach the element whose tab order is before the first element.
On Chrome, the first tab's focus will appear on the first text-order element no matter what tabindex is.
That means that tabindex is a nonworking solution if the DOM element is destroyed.
Attempt 2: The alternative is focus(). Inserting a ghost element with tabindex='-1' style='outline:none', and then calling focus() on it, does make tab focus on the element I want it to focus on. However, this has the problem of screwing up screenreaders. Whenever focus() is called, a screenreader (NVDA tested) might do one of three things:
read the new message text like I want it to (20% chance)
read the Window title, then tab title, then the focus()d element, which is horrible (70% chance)
read nothing at all (10% chance)
aria-hidden doesn't help. autofocus does nothing.
Attempt 3: That means focus() is not an acceptable solution. So the only alternative is to not destroy the DOM element. So the DOM element must be moved.
However, the moved-element graveyard will fill up if we continually push things there. And we can't delete a graveyard element if it contains our focus element, or else we'll be in the destroyed-focus-element situation. In my situation, interactive fiction will fill this graveyard up to gigabytes in a few hours.
Thus, when deleting history log elements, we must check the element and all its children to make sure focus is not contained there, and then dance around it if it is.
Attempt 4: However, it turns out that moving an element kills its focus anyway (at least on Firefox). So you can't just move it to a graveyard, you have to not touch it at all.
Once you have a surviving object that you haven't moved anywhere, you need to set tabindex='-1' on it, or else this graveyard object will mess with your tab order. However, if you do set its tabindex to -1, then (at least on Firefox), tab order now behaves like the destroyed-DOM-object situation once again.
Attempt 5:
don't move that disappearing object anywhere!
instead, take that object, and then hide it.
build your new object around it: the old object must be placed exactly where you want the tab placement to be, in text order. Your new content will be half before it and half after it.
check all of the hidden object's subnodes (recursively) for activeElement to find out where its focus is. For every non-active element that isn't a parent of the node with focus, you must delete it or else the screen reader will complain a lot. For the surviving nodes, make sure they are completely invisible and have no content, but don't use display:none, that will destroy the focus. Set tabindex = -1 on the focused node.
If your focus wasn't in the object you deleted, then find the object that owns the focus and set its tabindex to between the tabindex of the first half and second half.
I have run out of patience, and I am not going to implement this proposed solution, leaving mysteriously half-deleted, half-hidden elements in the middle of new text. After 5 failed attempts, I don't have much hope left, and I don't want to saddle maintainers with pages of in-depth comments, strange unintuitive behavior, and unexpected performance concerns, just for screenreaders. I am going to stick with my old focus() solution.
I have containers with multiple lines but only the first one visible (overflow:hidden). The container is expandable upon a click. (See https://stackoverflow.com/a/6972830 and the jsFiddle http://jsfiddle.net/JUtcX/2/)
If someone performs a Ctrl+F with text from the non-visible lines, the browser reports a match but cannot show it (because it's hidden).
How can I react to Ctrl+F and open the container whether a non-visible text in it was searched for?
[Update]
Approaches that do not meet all requirements:
Listening for Ctrl+F.
I have multiple containers and only want to expand those containing the search phrase. Upon listening for Ctrl+F I could only open all containers at once.
Does not work on all systems. This is a negligible defect only, though.
Chrome-specific workaround (link)
At least also Firefox should be supported
You can do something like this:
function find(e) {
if (e.ctrlKey && e.keyCode == 70) {
document.getElementById("hide").style.display = "block";
}
}
document.addEventListener('keyup', find, false);
#hide{
display: none;
}
<div>
ASDF:
<div id="hide">
Hidden
</div>
</div>
Listening to browser Ctrl+F/find layout modifications
I don't think it is possible to listen to those layout modifications.
When the browser find an element, it is equivalent to call
scrollIntoView for the matched element. Thus a scroll event will be
fired only if the container div is scrollable.
In the example, the parent style is overflow: hidden;. Thus it does
not trigger any scroll event.
It becomes then impossible to listen to these layout change, because
the only workaround that exist to listen to scroll event on
overflow:hiden element, is to listen to mouse wheel event ...
The bad story is that it is then impossible to prevent user from
modifying layout through the browser find, because even if one can
prevent Ctrl+F or F3, we can't prevent user from using the Edit-> Find
menu in Firefox or IE
JBE
Listen for Events from Browser "Find" Window in JavaScript
I don't know of any way you can listen for a find-like event and if
that's supported in any browser it sure isn't a portable solution.
I also don't know what you're trying to achieve but I think that your
best option is to listen for the keyboard events that trigger the find
window and attempt to cancel them while attempting to emulate the
find-toolbar/window with JavaScript of your own. This is however a
herculean (and nearly impossible) task due to some browsers
customization of keyboard shortcuts depending on the localization (for
instance, in IE, en-US uses Ctrl+F (for Find) while pt-PT uses Ctrl+L
(for Localizar, meaning find)).
Conclusion: I think you're out of luck there...
Miguel Ventura
Searching for text (Ctrl+F) across hidden spans
Chrome search feature (ctrl+f) finds hidden text ( but it's invisible! )
Lets imagine we have this sample code:
<input type="text" onblur="blurHandler()" />
<div class="results">
<ul>
<li>sampleText</li>
</ul>
</div>
Lets say you have currently focus on your input tag, and you hover to the "a" tag and click it. The browser will handle the onblur event first.
The task of the blur event is that it should hide the results div, but you still want to be able to click the link in the results div before that happens.
In blurHandler, use setTimeout() to delay hiding your div.
function blurHandler() {
setTimeout(function () {
//close the div
}, 100);
//do whatever else needs to be done
}
jsFiddle Demo
Another option would be to play with the mouseenter/mouseleave events on the link, and use a common flag between the event handlers so that they know about each other.
And one more: you can hide the div with a short animation, so it is actually still there when the click happens. Something like this:
$('.results').hide(1000);
jsFiddle Demo
Note: you should take a look at advanced event handling, inline event handlers can really mess up your HTML quickly. Separation of concerns helps others and your future self. If you use jQuery (seeing the tags under your question), you should use jQuery's event handling methods, which use the advanced model already.
I agree with Marcell's comment, though perhaps more from a usability perspective.
Assigning a timeout (as suggested by bažmegakapa) means you're choosing an arbitrary time limit that may or may not fire before the user has processed what they are supposed to do before that time limit is over. Unless your UI somehow makes it clear that they must react within a given time frame, this is likely to lead to frustrated users.
Even taking for granted that the users have had time to process the directions on screen, there's also transition time between moving from keyboard to mouse (or touch, where it's even worse as you have to deal with the UI shifting to hide the soft-keyboard), which means there's even more variance between different users' and their ability to follow the directions before the time limit you've chosen is over.
Just something to think about, in regard to how your interactivity is set up.
Most of user interaction elements associated to a custom JavaScript behavior in Web applications can be HTML links (a elements) having a meaningful href attribute value, enabling them to be used in non JavaScript-enabled environments:
<a id="profile" href="profile">Profile</a>
<script>
document.getElementById("profile").onclick = function() {
return !open(this.href, "_blank", "scrollbars=no,status=no"); // whatever
};
</script>
But some interaction elements are deeply linked to JavaScript, either because the Web application they are contained in requires JavaScript to run or because they were generated by JavaScript and don't make any sense when it is not available.
For those, as I want users to be able to interact with them whatever device they are on (i.e. I don't want to define mouse, keyboard, touch, … interaction by myself on a span element), I see two relevant HTML elements: a and button.
My problem with the a element here is that it defines at least one behavior I don't want: the ability for the user to open its target anywhere he wants to (e.g. in a new tab), whereas the interaction I want to take place is specific to the current tab.
My problem with the button element here is that, as far as I can tell from the online resources, it is difficult to style reliably on all modern browsers (but I am not sure if it is still the case now).
Some of the facets of this question have already been answered elsewhere, but I can't find a comprehensive and up-to-date summary: what HTML element would you recommend to use?
If you want an element to semantically be a button without the style issues of a <button> element, or behavior of an <a href> element, then you should use an element with [role="button"]. <span> is commonly used, but pretty much any element could be used.
<span role="button"></span>
Now, [role="button"] is really just a flag for assistive technology, so some interactions need to be set up to react as a button, but they're actually quite easy.
Buttons (such as links and form elements) are typically tabbable. This isn't always necessary, such as if a keyboard shortcut has been set for it already. If you want the <span> in the tabbing order, just add the [tabindex] attribute:
<span role="button" tabindex="0"></span>
Now you can tab to the button, but you'd probably still want to trigger the click event when Enter and/or Space is pressed.
Simply adding an event listener to the button is enough.
For brevity this example uses jQuery:
$(document).on('keydown', 'span[role="button"]', function (e) {
if (e.which === 13 || e.which === 32) {
$(this).click();
e.preventDefault();
}
});
This uses an event delegation format to provide click support for all spans with [role="button"], you may want to choose a different selector depending on your situation.
Now all that's left is to listen for when the button is clicked:
Again, jQuery:
$('.myButtonClass').click(function () {
...do stuff...
});
Now, for other devices, you're going to want to trigger a click on, say, a touch event. If you're using jQuery, there are assorted libraries to support turning touch into click and/or tap. If you're not using jQuery, it's not a lot of work to listen for touch events.
I'm not going to provide a code example to handle touch, but that's because it depends on what the button is supposed to do. In some cases you want to trigger a handler simply by starting a touch on the button (equivalent to mousedown), in other cases you want to trigger the handler if you've started and stopped the touch event on the same element (similar to how click works normally).
I have some questions about focus in general (adhering to WC3 format, screw IE). I'm having problems with unintended actions occurring on a widget i am building (using the Dojo Toolkit), and i believe a better general understanding of focus will allow me to solve my problem myself, so here goes:
First of all, which common HTML element are and AREN'T focusable? I've been trying to throw focus around and it works sometimes, and doesnt work other times...
What is the 'highest' level focusable on a page? For instance, can i focus the window? The <body> tag? Specific to dojo, can you focus an entire widget? If the template is widgeted can you focus just highest level of the template (usually a <div>)?
Can focus be 'removed'? Can i remove focus from all elements/objects on the page until the next object is focused? Can i prevent a element from being focusable (like a button)?
What are all the methods through which i can affect focus? Besides calling the focus() method on elements, can focus be set through HTML attributes or in a CSS?
Thanks in advance for what i hope to be some great answers!
Disabled controls can't receive focus according to the HTML4.01 spec
Firefocus, an extension that works over Firebug, could be a great enhancement if you're asking this sort of questions :) Install, restart and look at the Console
Related to focus is the order in which elements are given the focus, that is the order of tabbing navigation and the tabindex attribute with its values -1, 0 or positive when it exists
Any DOM element can receive focus amongst other some event handlers.
See answer 1.
Focus is passed around and not removed.
Focus can only be set by the DOM using JavaScript.
You can also use dojo.place to put things in the correct for focus.