Advanced HTML: Liberate focus order from the hell of tabindex
Focus order, and making sure each element can be tabbed-on to at the right time, is crucial for usability, accessibility and happy customers. But managing focus order using the tabindex
attribute is hell for everyone. Fortunately, there are better techniques we can use.
Why is focus order so important?
Imagine you are a sighted user, filling out a form to buy tickets on a cinema theatre’s website. There are three inputs: name, email address, and a tickbox to confirm you agree to the terms and conditions. They are presented in that order on screen. You enter your name, hit the tab key expecting ‘email address’ to get focus, and the tickbox underneath gets focus instead. You tab again and the Facebook link in the footer gets focus. Tab again and you’re now in the main menu at the top. You are lost and irritated.
That’s an awful user experience. If you’re able to use a pointing device you might be able to get around the problem by clicking on each of the inputs to focus them. But if you’re relying solely on the keyboard or equivalent device, you’re stuffed. Worse still if you have limited or no vision and are trying to do all this with a screen reader.
For many users, this failure of the developer to properly manage focus order means they can’t complete the task. Which is bad for them. And bad for you if they can’t buy your product. Even worse for you if they choose to sue.
The simple rule is to make focus order follow the content’s presentation order. That should be true whatever technology someone is using to access your website, page or feature.
What does WCAG say about focus order?
The Web Content Accessibility Guidelines (WCAG) include guidance on focus order that you should have a good read of, specifically WCAG 2.1 Quick Reference 2.4.3 Focus Order and WCAG 2.2 Understanding SC 2.4.3 Focus Order (Level A).
Note that 2.4.3 Focus Order is a Level A success criteria. Meaning it’s considered a basic requirement of usability, accessibility and quality.
What is tabindex?
One commonly-used solution for managing focus order is to add the tabindex attribute to any html elements requiring focus. The value of tabindex determines the position each element appears in the focus order.
By giving every element tabindex with a specific, sequential, positive value, you can have military-grade control over the order in which things are able to be tabbed to. Which sounds great.
If you set it to a negative value, you take the element out of the tab sequence all together. Which is cool if you have a button positioned off-screen in a dialog box, because you can use JavaScript to allow focus of the button later, when the dialog is in view. Neat, huh?
You can even add tabindex to elements that aren’t usually focus-able, such as a div , span or even a paragraph <p>
! Set a positive value to choose its position in the focus order, or set it to 0
(zero) to let the browser decide where it goes. Which is all great if you’re building a custom component, or some cool effect, right?
I could go into lots of technical detail about tabindex here. But I’m not going to. Because I don’t want you to use it, and I’ll explain why below. Although I do still recommend you read about tabindex on mdn web docs_ so you properly understand what it is and what it does.
Why is tabindex a bad solution for maintaining focus order?
Maintenance
If your project uses tabindex and your focusable elements have sequential, positive numbers, you’ll know it’s messy and hard to maintain. Imagine you have a page with 20 focusable elements all with tabindex values of 1 to 20, and someone asks you add a new field to the form, or a new menu item. You’ll have to updated everything. That’s a lot of work. Maybe there’s some clever Javascript to automatically update all the values. That’s still extra effort and complexity and is bound to become a pain to maintain.
Easy to make mistakes
Quite often, when tabindex is used, tabbing just doesn’t follow the order in which the content is presented despite the best efforts of the developers, simply because it was hard to maintain and it broke. It’s unnecessarily complicated and easy to mess up – especially for any developers picking up the project later.
Focussing things that shouldn’t be
Making elements focusable when they shouldn’t be is generally a bad idea. But you’ll see it done. Users only really expect to be able to focus on things like buttons, inputs, select menus and anchors. If you find an element that’s been made focusable when it shouldn’t be, figure out why it’s designed that way and propose a better solution. If you are making a custom component, try your best to use native HTML components and restyle them (which should always be your preferred option). If you can’t do that, you may need to use tabindex, but follow best practices for accessibility and usability (refer to WCAG and WAI-ARIA for starters), and test the heck out of it with different people and end-user technologies.
Removing things from focus
You might also find some things you’d expect to be able to focus on – like a button or an input – have been made un-focusable because of tabindex with a negative value. Which can be very confusing, disorientating and annoying for users. If you find that happening in your project, find out why it’s designed that way and propose a better solution. A user experience designer can help you explore options.
Masks underlying problems instead of fixing them
Often tabindex is used to mitigate issues caused by badly written html and a messy document structure. Which is a bad solution, because it creates more mess, complexity and misery for end users.
How should focus order be maintained?
Thankfully, we can avoid having to use tabindex by following some simple best practices. These techniques aren’t just better for coding standards: they provide users with a superior experience.
1. Put everything in the HTML in the same order it’s presented to users
Often, tabindex is used to solve problems caused when the html is mixed up. Un-mixing the html is a much better solution.
The natural order of focusable elements – and the natural reading order of the content – is determined by one simple thing: the order in which those elements appear in the HTML.
Having all the content in the correct order in the html is crucial for many users of assistive technology. In most cases it’ll make the page easier to maintain too, because it’ll make more sense to future colleagues. Read more about it on the WCAG Quick Reference for 1.3.1 Info and Relationships and WCAG Technique SCR26: Inserting dynamic content into the Document Object Model immediately following its trigger element.
2. Properly hide hidden elements
Often, you’ll find an element has been positioned visually off the screen and has tabindex="-1"
added to it (or its children) to stop users tabbing on to it. And then there’s javascript to toggle the value on it and all its children when the element is moved into view. Which might work for a sighted user with a standard visual display, but it’s still in the document. So assistive technology users, such as those using a screen reader, will still hear the element, unless even more special attributes are added. This is a lot of extra work, complexity and risk when there’s a simple and robust alternative at hand.
When an element has the css declaration display: none
applied, it’s hidden completely from all users, regardless of the technology they are using to access the page. It, and all of its focusable children, are automatically removed from the accessibility tree and can’t be read or focussed while display: none is applied. Need to show the element and make things focusable and readable again? Just remove display: none
. It’s that simple.
3. Manage focus and reading position based on user interaction
If you’ve hidden a menu or dialog using display: none , and have now made it visible, or the new element is only being loaded into the HTML when the user requests it, you need to make sure focus and reading position move to where the user expects it.
There are two main ways of doing this:
- Put the newly-available element into the HTML directly after the element that triggered it. Learn about this technique at WCAG 2.2 Technique SCR26: Inserting dynamic content into the Document Object Model immediately following its trigger element.
- Use Javascript to move focus (or reading position) to the newly-available element.
Whichever technique you choose, you will often also need to:
- Trap focus inside a menu or dialog, so when the last item has focus, the next tab press cycles back to the first item in the group (instead of jumping to something in the underlying document)
- Add WAI-ARIA attributes such as aria-expanded on the trigger element.
- Return focus to the trigger element when control is eventually closed, hidden or removed.
Tip: Reading position is important for some assistive technology users, such as those using a screen reader. For example, in a dialog, the first focusable element might be the ‘OK’ button at the bottom. But that comes after important content. The screen reader’s reading position needs to be at the start of the content so the user can read the information before reaching the button. Here you can use element.focus(); to target the parent container, rather than the button. As a non-focusable element it won’t actually get focus, but the reading position will be moved to the most appropriate place for the user to continue. The next tab press will naturally focus the OK button.
4. Use bypass blocks
When you arrange all your content in the html to match the presentation order, you’ll almost certainly find the header, main navigation and even a sidebar of links all appearing before the main content. And you’ll be thinking that’s a lot for someone to have to tab though every time a page loads. And you’ll be correct. That’s what bypass blocks (also known as ‘skip links’) are for.
Open a well made web page, and press the tab key (you may have to tab a couple of times to get past things like logos; brand narcissism is still alive and kicking). You will likely see a link appear in the top left corner (or hear the link’s label if you’re using a screen reader). The label will be something like: “Skip to main content”. Click it and you will jump past all the headers and menus directly to the start of the main content. That’s a bypass block in action. It’s brilliant for enabling keyboard users to effortlessly move around the page.
And it’s really easy to implement. Read about it on WCAG 2.2 Quick Reference 2.4.1 Bypass Blocks.
Tip: If you also make sure you use proper landmarks in your html, it gets even better. Read about those on WCAG 2.2 Technique ARIA11: Using ARIA landmarks to identify regions of a page.
5. Use native HTML elements whenever possible
Always use the correct html element for the content or interaction you’re adding to the document. I can’t stress this enough.
HTML is a fantastic markup language. Every element has a specific purpose and semantic meaning. Every element and type has built in features that save you work as a developer and make users’ lives easier.
Take <button>
as an example. Browsers, assistive technology users, search engine crawlers, fellow engineers, can all know it’s a button. And it automatically goes in to the document’s focus order. A messy alternative might be <span role="button" tabindex="4">
(yuck) which will also miss out on every other bit of native functionality and performance gain you’d get from simply using <button>
, placing it in the correct position in the html and adding a bit of css.
Can’t remember every element? Can’t imagine ever learning them all? That’s fine. Neither can I. Just get in the habit of asking yourself “what’s the best element for this piece of content or interaction” and you’ll find the answer without much effort.
Summary
If you come across tabindex in your project, or test with a keyboard and find focus order is a mess, you now know how it can be fixed.
And remember, to write ‘advanced’ HTML, all you have to do is keep things simple.