Notes of Maks Nemisj

Experiments with JavaScript

Focus, tabIndex and behavior of browsers

“Do not trust defaults”

# Prefix

Recently I had an opportunity to deal with focus behavior in browsers, with tabindex attribute and other focus related stuff. When I was doing this work I came across interesting aspects of focus implementation in different browsers. This article is exactly about all these things. Tested browsers are IE (6,7,8), Firefox (linux-3.6.3) , Chrome 4.x, Safari 4.0.4 , Chromium(5.x linux), Opera 10.5.

# Stem

Before I start, let me remind you some basics about focus, tabIndex and user interaction.

As you know tabindex is used to specify focus order of the nodes and enable focus. I say, enable focus, because there are nodes which are not focusable by default, like div, span, table, etc, but can receive focus if tabindex is applied. Element receives focus when clicked by mouse (or touched on touch screens ) or when tab key is pressed. I’ve divided this article into two parts : “Interaction by tab-key” and “Interaction by click”. There has been a lot written about tab pressing interaction, so I will keep it briefly. The main part of this article will be “focused” on “focus with mouse click”.

For testing purpose I took different tabindex values as in example below :

  <input type="text" value="tabindex is not here" />

  <div> not tabindex </div>

  <input type="text" tabindex="-1" value="tabindex is negative" />

  <div tabindex="-1"> tabindex is negative </div>

  <div tabindex="0"> zero or positive </div>

  <input type="text" tabindex="0" value="positive or zero" />

  <div tabindex=""> omitted tabindex </div>

## Tab-stops

First, let’s refer to the w3c documentation to fully understand the tabIndex meaning in the tab-press story. The most important of all in that link is the tabIndex value. Which defines if node should be included in tab-press sequence – equal or greater than zero, or discarded from this sequence – negative value. If value is omitted it has different meanings based on node type. For non focusable elements it means nothing, while for inputs it will have effect like zeroed tabindex.

All the theoretical information is repeated, time to start and test the browsers’ implementation. The result of my titanic work of that test you can see below 🙂

Attribute in markup Node is focused
input tabindex = “-1” no
div tabindex = “-1” no
div tabindex = “” no
input tabindex = “0” yes
div tabindex = “0” yes
div (no tabindex) no
input (no tabindex) yes

Table: Node is focusable when performing tab-press

Tab-stop sequence in all browsers is exactly the way it’s described by W3C. Focus is placed only on nodes which have tabindex equal or greater than zero. Default nodes like input, do not need any tabindex to be included in tab-stop sequence. Though if tabindex is negative, all browsers skip this node from tab sequence. Well, it’s all clear. Nothing more to say about this subject, so let’s move to the next stage.

## To click or not to click

This test was a little bit controversial for me. From the user perspective – focused node is the node which gets outlined or selected by some other kind of visual selection ( border dotted, blue, etc). From us, developers perspective, focused node is the node which receives onfocus event and it does not matter whether focus is visible or not.

Of course, end-user is the person who will work with an application. We have to originate from his point of view, which means that visual style is everything. If node is not visually selected, it does not exists as focused element for user. That’s why my test was aimed at end-user experience – “Hey, when I click nothing happens…it’s broken” – or something like that.

Because input elements can always receive focus ( when enabled ), I did not include them in the results below:

markup attribute Firefox IE WebKit * Opera
div tabindex = “-1” yes no yes no
div tabindex = “0” yes yes** yes no
div tabindex = “” yes no no no
div (no tabindex) no no no no

Table: Node is focusable when clicking it

* Safari and Chrome both uses Webkit render engine and have the same results

** IE bug

That’s sorrowful. There are no identical browsers which have the same focus implementation, except Safari and Chrome (I assume only because they both use WebKit engine).

One of the first things which came to my mind, is that, it’s still not possible to rely on the browser’s default behavior. One browser will make visual style, while other will not. It could be okey, if we were working only with positive or zeroed tabindex. But it is 2010 and ajax applications are sometimes more complex than desktop’s are, it’s not possible to pretend that no one uses negative values for tabindex. So… such a big difference in outline implementation left us not a big choice. If you want to have visual accent on focused nodes, use your own CSS classes, like most of us do for the :hover elements or stop using negative tabindex at all.

Beside all of that weird stuff I’ve also noticed two strange aspects in Firefox and MS Internet Explorer.

It appears that only Firefox is the browser which shows outlined node when tabindex is presented as attribute but value is omitted. I do not know, if it was a feature or not, but keep in mind, that

node.setAttribute("tabindex","");

will make node focusable by click.

Another interesting issue is non focusable nodes in IE. It seems that nodes like div, which have tabindex zero or greater than zero do not receive focus by click. Yet, they will start receiving click focus if such node has been focused by tab-press. After node has been focused once (by tab), it will start receiving focus by click (even if you reload the page). Though, if you delete browser’s cache and open the same page again, node becomes non focusable back. Really weird… If interested – Try it yourself.

UPDATE: Tested in IE6/7 in mac (with wine) and the bug is not possible to reproduce.

## To dispatch or not to dispatch

After end-user test there is still one open item. Ajax applications are event-driven, so how does tabindex influence “onfocus” event? Does it get dispatched or do we stay unaware like usual users do? Well, check the results :

Attribute in markup Firefox IE WebKit * Opera
div tabindex = “-1” yes yes yes yes
div tabindex = “0” yes yes yes yes
div tabindex = “” yes no no no
div (no tabindex) no no no no

Table: Node fires onfocus event

What do we have here? It appears that even if node is not visually outlined, it still fires an onfocus event. All of the major browsers behave the same, except that Firefox keeps receiving focus with omitted tabindex value.

## Detecting focusable node through JavaScript

To say frankly, I’m a bit meticulous person, I always try to understand internal part of the subject. In situation with focusable nodes, I was wandering what information does browser expose to the JavaScript environment, depending on the tabIndex and node type (like input and div). As an challenge I decided to write code which could detect any focusable element, despite the focus visualization. Even that such functionality has not much to do with daily work, but maybe for some of you it will be useful.

Actually defining if node is focusable or not does not look difficult. Node with negative or positive value is focusable by click ( at least can receive onfocus event ). Node with positive or zero is focusable by tab-press. In theory the only left thing is to get appropriate tabindex value of the node and check if node is focusable by default. Easy… 🙂

At that time I knew, that getting tabindex value can be done in two different ways. First by using property node.tabIndex, described at MSDN. Or by using node.getAttribute("tabindex") function.

I used both functions to see the differences and to decide which one I need. In a result table below I grouped all browsers, except IE, due to the fact that IE gives totally other results than the rest.

Attribute in markup tabIndex getAttribute tabIndex ( IE ) getAttribute ( IE )
tabindex = “-1” -1 -1 -1 -1
tabindex = “” -1 “” 0 0
tabindex = “0” 0 0 0 0
div (no tabindex) -1 null 0 0
input (no tabindex) 0 null 0 0

Table: Different approaches in getting tabIndex value

Because of these zeros in IE column, first I will analyse “all” browsers and then I will return to IE.

When looking at the results I can mention a number of positive aspects. First of all it’s really nice that browsers respect markup and give the correct value via getAttribute function meaning that getAttribute is trustable, but in IE. The second valuable aspect is that browsers return positive or zero value if node is focusable via tab-press even if no tabindex is specified. You can see that in “input” node column, which does not have any tabindex, but still tabIndex returns 0. This indicates, that browsers even help us to identify focusable nodes.

Yet, there is one little pitfall. What first hits the eye is the tabIndex which is -1. Browsers return -1 in three different cases when querying div :

  • When tabindex is not specified
  • When tabindex is specified as -1
  • When tabindex is omitted

You may ask, why is it a pitfall, browsers return -1 to any node which is not inside tab-press sequence? Fair enough. But please do not forget, detection code should identify all focusable nodes, which includes nodes focusable by mouse click.

Gathering all information together it become clear for me that through all the good things tabIndex property is doing for developers, I still have to use both approaches.

Instead of limiting myself to the isFocusable( node ) function, I decided to write generic getTabIndex( node ) function. This function would return correct tabindex value only if node is focusable ( dispatches onfocus event). Furthermore knowing IE issue with tabIndex/tabindex, I can also include the workaround to retrieve correct tabIndex for MSIE.

For now, based on the getAttribute and tabIndex, getTabIndex will looks like this :

window.getIndex = function (node) {
    var index = node.tabIndex;
    if (isIE) {
        // later
    } else {
        if (index == -1) {
            var attr = node.getAttribute("tabindex");
            if (attr == null || (attr == '' && !isFirefox )){
                return null;
            }
        }
    }

    return index;   
}

I think the code is self explaining and I do not have to dive into the explanation. Btw, as you can see, I’ve included check for firefox (let’s respect his own small weirdness) 🙂

## IE tabIndex retrieval

Now, lets’ look to the Internet Explorer results. IE returns zero value in a lot of different cases. The only case when IE returns correct value is when tabIndex is specified on node. So how can we determinate if node is focusable or not?

First of all we should get real value from markup and because getAttribute(“tabindex”) is not working, it’s time to find something different. The answer hides inside “attributes” property of the node. It’s possible to query attribute of the node by using node.attributes.tabIndex which will return an AttributeNode object. Though this node in most cases has value of 0, AttributeNode also consists of “specified” property, which show the real state of markup.

Here is the short IE getTabIndexAttributeValue function based on the info :

function getTabIndexAttributeValue(node) {
        var ti = node.attributes.tabIndex;
        return ti.specified ? ti.value : null; 
}

Yet, there is another problem with IE tabIndex. As I explained above, other browsers return zero also for nodes, which are focusable by default, like input, textarea, etc. IE also does that. However it does that also for nodes which do not receive focus by default, for example for div (and even more weird for <BR/> ) :O. Solution? Take names of nodes which are focusable by default and match them with given node. These names are listed in the spcecs : a, BODY, button, frame, iframe, img, input, isIndex, object, select, textarea

Well, here is the final version of getTabIndex :

window.getIndex = function (node) {
    var index = node.tabIndex;
    if (isIE) {
        // late
        var tnode = node.attributes.tabIndex.specified;
        if (!tnode) {
            var map = { a:true, body:true, button:true, 
                frame:true, iframe:true, img:true, 
                input:true, isindex: true, object: true, 
                select: true, textarea: true
            };

            var nodeName = node.nodeName.toLowerCase();
            return (map[nodeName])  ? 0 : null ;
        }
    } else {
        if (index == -1) {
            var attr = node.getAttribute("tabindex");
            if (attr == null || (attr == '' && !isFirefox )){
                return null;
            }
        }
    }
    return index;   
}

# Suffix

I hope you found also some useful information in this note, like I did, when I was testing. And one of the things for me was : IMHO : If you need visually selected focused elements, do not rely on browsers’ implementation. Just get rid of the active border and use your own highlighting mechanism.

Used links :

, , , , ,

8 thoughts on “Focus, tabIndex and behavior of browsers

Leave a Reply

Your email address will not be published. Required fields are marked *