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:
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:
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.