Tab Order

While accesskey has its problems, there’s little reason to ignore the tabindex attribute. Setting a useful tab order is good for all keyboard users, including those using screen readers or onscreen keyboards. Most mobile browsers ignore tabindex in favor of navigating with the arrow keys—which makes sense when you remember very few of them have a Tab key to begin with. Because of this, tabindex fits with the principles of universal design: it offers a benefit to one class of users, without side effects for others.

Note

Be sure to keep tabindex values up-to-date. When new content is added, test that the tab order still makes sense and update tabindex values accordingly.

The tabindex attribute is not called for unless the fields being tabbed to are somehow out of order. Most commonly, this happens when using a layout table to form two columns of form controls:

<table>
    <tr>
        <td>
            <label for="firstname">First name:</label>
            <input type="text" name="firstname" id="firstname" />
        </td>
        <td>
            <label for="cardtype">Credit Card: </label>
            <select name="cardtype" id="cardtype">
                <option>MasterCard</option>
                <option>Visa</option>
                <option>American Express</option>
            </select>
        </td>
    </tr>
    <tr>
        <td>
            <label for="lastname">Last name:</label>
            <input type="text" name="lastname" id="lastname" />
        </td>
        <td>
            <label for="cardnumber">Card Number:</label>
            <input type="text" name="cardnumber" id="cardnumber" />
        </td>
    </tr>
</table>

This code is for a simple table with first and last name on the left and credit card information on the right. But structurally, the name and credit card fields are interleaved, and this is how they will appear in the tab order. Users will go from first name, to credit card type, to last name, to credit card number, and so on, down the page. For anybody who uses the Tab key to move between fields, this can be disorienting.

We can work around this by setting the tabindex attribute on our form fields, like so:

<table>
    <tr>
        <td>
            <label for="firstname">First name:</label>
            <input type="text" name="firstname" id="firstname"
             tabindex="10" />
        </td>
        <td>
            <label for="cardtype">Credit Card: </label>
            <select name="cardtype" id="cardtype" tabindex="110">
                <option>MasterCard</option>
                <option>Visa</option>
                <option>American Express</option>
            </select>
        </td>
    </tr>
    <tr>
        <td>
            <label for="lastname">Last name:</label>
            <input type="text" name="lastname" id="lastname"
             tabindex="20" />
        </td>
        <td>
            <label for="cardnumber">Card Number:</label>
            <input type="text" name="cardnumber" id="cardnumber"
             tabindex="120" />
        </td>
    </tr>
    ...
</table>

Now the tab order will flow from first name (10) to last name (20) to card type (110) to card number (120). We use multiples of 10 here because it’ll be easier to add, say, a middle name or a cardholder name, in between these existing fields later on. We assume that there may be up to 10 fields on the left, so we just jumped by 100 to the right. We have plenty of space with tabindex—up to 32767—so we may as well use it.

There is, however, a better way. Since mobile users don’t have wide displays, they’re not going to want to scroll sideways to move from field to field. In general, it’s better in cases like this to structure our form the way we want it using the language’s built-in features, and style it separately.

In the case of our form shown earlier, we can do something like this instead:

<div id="column1">
    <label for="firstname">First name:</label>
    <input type="text" name="firstname" id="firstname" />
    <label for="lastname">Last name:</label>
    <input type="text" name="lastname" id="lastname"/>
</div>
<div id="column2">
    <label for="cardtype">Credit Card: </label>
    <select name="cardtype" id="cardtype">
        <option>MasterCard</option>
        <option>Visa</option>
        <option>American Express</option>
    </select>
    <label for="cardnumber">Card Number:</label>
    <input type="text" name="cardnumber" id="cardnumber" />
</div>

Then we can use CSS to lay them side by side:

#column1, #column2 {
    width: 49%;
    border: 0;
    margin: 0 auto;
    padding: 3px 0;
}
#column1 {
    float:left;
}
#column2 {
    float:right;
}

Now, most mobile devices will just ignore your CSS, or read your separate handheld style sheet. If you leave out styling on those divs, you end up with a form that displays very cleanly in a single row.

But wait. What happened to our tabindex values? We don’t need them! Most of the time, if you structure your code as simply as possible, there’s no need for tabindex. But it’s good to know that it’s always there when you really need it.

We may be able to improve our form even more by using some more semantics. Let’s rebuild this form using a definition list, or dl. The dl element and its children, dt and dd, are meant to convey a relationship between two items. According to the specification, it need not be, strictly speaking, a definition.

<dl id="column1">
    <dt><label for="firstname">First name:</label></dt>
    <dd><input type="text" name="firstname" id="firstname" /></dd>
    <dt><label for="lastname">Last name:</label></dt>
    <dd><input type="text" name="lastname" id="lastname" /></dd>
</dl>
<dl id="column2">
    <dt><label for="cardtype">Credit Card: </label></dt>
    <dd><select name="cardtype" id="cardtype">
        <option>MasterCard</option>
        <option>Visa</option>
        <option>American Express</option>
    </select></dd>
    <dt><label for="cardnumber">Card Number:</label></dt>
    <dd><input type="text" name="cardnumber" id="cardnumber" /></dd>
</dl>

We gain a few more side benefits with this approach. First of all, the CSS we just wrote works the same as before. dl, like div, is a block element, so we can set margin and padding values on it. And we can gain two small victories for certain groups of users:

This is exemplary of the universal design aesthetic. We’ve used our language’s built-in functionality to improve the experience for everyone, by giving each user’s device enough information to render the form more appropriately. And we can make changes like this in a gradual fashion, without disorienting our users.

We run into tabindex again in Chapter 8.