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.
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
div
s, 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:
For users of mobile devices that don’t support CSS, the label and the form field are now associated in a manner that is relevant for their devices.
We may prefer one number spanning both columns, but some information is better than none. Like we said, it’s a small victory.
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.