Using a Keyboard and Focus Order
There is a WCAG criterion stating that users should be able to navigate and interact with a site using only a keyboard.
Keyboard
All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints.
To interact with links, buttons, and other elements using only a keyboard, users can navigate through a site by tabbing, following what's known as the focus order. This may not be apparent to those who interact with a site using a mouse or touch, and it is one of the most common sources of accessibility issues.
Focus Order
If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.
Example: Test the Focus Order on My Site
Here's an example site you can use to test the focus order by tabbing through all elements that can be interacted with (usually this means elements that can be clicked on with the mouse pointer or by using the enter key).
The following code is a bit stripped down but demonstrates the basic structure of the site, which is what we are interested in here. The only relevant CSS is that the dialog box has position: absolute
and some styling to put it roughly in the middle of the site.
const [showDialog, setShowDialog] = useState(false);
<h1>Example Focus Order Site</h1><nav><a href="/">Home</a><a href="/about">About</a><a href="/contact">Contact</a></nav><hr></hr><main><p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Laboriosam, eius.</p><labelid="dropdownLabel"htmlFor="dropdown">Choose an option:</label><select id="dropdown"<option value="Option 1">Option 1</option><option value="Option 2">Option 2</option><option value="Option 3">Option 3</option></select><buttononClick={() => setShowDialog(true)}>Open Dialog</button>{showDialog && (<div><p>Example Message</p><buttononClick={() => setShowDialog(false)}>Close</button></div>)}</main><hr></hr><footer><a href="mailto:example@mail.com">Email me!</a></footer>
You can bring the focus to the Home link with the button at the top. Move focus by using tab on your keyboard, the element will get an orange background.
Clicking the "Open Dialog" button will open a dialog box.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Laboriosam, eius.
Focus will shift to the next element in the focus order, further down in the code. However, sometimes you might have a popup or module that you want to give focus after a certain action.
Take the dialog box from the example above: In order to ensure that the focus shifts to the "Close" button after the "Open Dialog" button, it needs to be the next interactable element in the code.
However, since the box is styled with position: absolute
it could be put somewhere else. Here's an example where the box is positioned at the top within the <main>
element instead:
Example: Unpredictable Focus Order
<main>{showDialog && (<div><p>Example Message</p><buttononClick={() => setShowDialog(false)}>Close</button></div>)}<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Laboriosam, eius.</p><labelid="dropdownLabel"htmlFor="dropdown">Choose an option:</label><select id="dropdown"<option value="Option 1">Option 1</option><option value="Option 2">Option 2</option><option value="Option 3">Option 3</option></select><buttononClick={() => setShowDialog(true)}>Open Dialog</button></main><hr></hr><footer><a href="mailto:example@mail.com">Email me!</a></footer>
A user with a mouse or touch screen would not notice any difference, but a user that is tabbing through the focus order will notice that the next element to get focus after the "Open Dialog" button would not be the "Close" button. Instead, it would be the "Email me!" link in the <footer>
, forcing the user to tab all around the site until focus finds the "Close" button.
Changing the Focus Order
Finally, let's take a look at how you can change the focus order if necessary.
Example: Changing the Focus Order
There are a couple of methods to alter the focus order. The tabindex
attribute can be used to decide the exact sequential order of the focus as 1, 2, 3 and so on. It can also make any element focusable, even if a user can't interact with it.
<nav><a href="/" tabIndex={1}>Home</a><a href="/about" tabIndex={2}>About</a><a href="/contact" tabIndex={3}>Contact</a></nav><p tabIndex={4}>This element is not clickable but will recieve focus.</p>
Note that in React, the attribute uses camel case, tabIndex
.
As a default, elements without a specified tab index have an index of 0 and will shift to the next clickable element in the code structure as we've looked at above. Using a negative tab index will remove elements from the focus order and render them impossible to reach with focus.
MDN recommends avoiding using a tabindex
with a value above 0, and if used it must follow the relationships in the content.
You can also use the ref
tag and the focus()
method to move the focus directly to another element. Here's an example where I keep the dialog box above the button that opens it in the code.
I add a ref
tag to the "Close" button, allowing me to target it. To shift the focus I add a focus()
method within a useEffect
that listens to the showDialog
state.
This way the dialog box will open and focus will shift to the "Close" button:
const [showDialog, setShowDialog] = useState(false)const closeBtnRef = useRef(null)useEffect(() => {if (showDialog && closeBtnRef.current) {closeBtnRef.current.focus()}}, [showDialog])
<main>{showDialog && (<div><p>Example Message</p><buttonref={closeBtnRef}onClick={() => setShowDialog(false)}>Close</button></div>)}
Example Altered Focus Order Site
Lorem ipsum dolor sit amet consectetur adipisicing elit. Laboriosam, eius.
A few notes:
The next element in the focus order after the "Close" button is the dropdown <select>
, which probably will be confusing for a user. This is not desirable since the focus order should be predictable and easy to understand.
While using the ref
tag and the focus()
method can come in handy, it's a good idea to use them sparingly. Aim to set up your site so that the focus order is straightforward and easy to follow.
I'm using a non-semantic <div>
for the dialog box, but you should use the <dialog>
element which comes with some built in functionalty and even moves focus to the dialog automatically when opened. Read more over at MDN and consult CanIUse to find out which browser versions support the element.