What's an interactive element?
posted on
Two years ago, I wrote an article about the dialog element. I tested where focus goes when you open a modal dialog via the showModal() method. I tried different combinations of elements and attributes to see what happens because back in 2023, the behaviour was very inconsistent.
In one of my tests, I put the tabindex attribute on the dialog element. That's why Joy contacted me earlier this week, because she was unsure whether this was a good idea since the page on MDN about the dialog element says the following:
And here's the usage note:
Do not add the tabindex property to the
<dialog>element as it is not interactive and does not receive focus. The dialog's contents, including the close button contained in the dialog, can receive focus and be interactive.
I recommended to ignore this warning and I also promised to give a proper, more detailed answer in a blog post.
Disclaimer
Before I share my opinions with you, let me clarify that this post is not about MDN. MDN is one of the few websites I visit every day, and I'm a big fan. I also understand that the content is open source and a community effort. One will likely disagree with some opinions at some point.
Using tabindex on the dialog element
I couldn't stop thinking about this warning the entire week because I don't understand why the authors put so much emphasis on it. There is this big fat red warning early on the page saying that you must not use the tabindex attribute on the dialog element.
I know where this is coming from. In the HTML spec for the dialog element it says:
The tabindex attribute must not be specified on dialog elements.
So, technically, they’re right because the spec says that you must not use the tabindex attribute on the dialog element. I don’t understand why, though, or at least I don’t understand the phrasing, because it’s absolutely fine to use the tabindex attribute on the dialog element. It doesn’t break anything. The only reason I can think of not to use it is that it’s not necessary. The dialog element itself already handles focus well. I don’t know if I’m missing anything, but I would rephrase that to something like “It’s not necessary to put the tabindex attribute on the dialog element”.
I'm annoyed by the prominent styling of this warning, but what's really bugging me is what they have to say in the usage note.
Do not add the tabindex property to the
<dialog>element as it is not interactive and does not receive focus.
Both statements are wrong. The dialog element does receive focus because it's an interactive element. Try it yourself.
Last focused element:
HTML:
<dialog>
<h1>Demo 1</h2>
</dialog>
JS:
button.addEventListener('click', e => {
dialog.showModal();
output.textContent = document.activeElement.tagName
});
Although it sounds like I'm writing this blog post because I find these statements so horrible, I'm not. I'm writing it because I feel like there's a general misunderstanding of what an interactive element is and what focusable really means. I wasn't 100% sure either, so I did some research.
Interactive elements
The first sentence in the section about focus in the HTML spec is:
An HTML user interface typically consists of multiple interactive widgets, such as form controls, scrollable regions, links, dialog boxes, browser tabs, and so forth. These widgets form a hierarchy, with some (e.g. browser tabs, dialog boxes) containing others (e.g. links, form controls).
The dialog is an interactive element that can contain other interactive elements. I could stop here because I already have the information I need, but let's keep going. Interactive elements can be focused, which makes them focusable areas.
Focusable areas are:
- Elements that meet all the following criteria: (see examples below)
- The element's tabindex value is non-null, or the user agent determines the element to be focusable
- The element is either not a shadow host, or has a shadow root whose delegates focus is false;
- The element is not actually disabled;
- The element is not inert.
- The element is either being rendered, delegating its rendering to its children, or being used as relevant canvas fallback content.
- The shapes of area elements in an image map
- The user-agent provided subwidgets of elements
For example, the controls in the user interface for a video element, the up and down buttons in a spin-control version of<input type=number>.<video controls> </video> - Scrollable regions of elements
A div can be an interactive element, too. You probably want your scrollable element to be a labelled semantic element, though.<div style="inline-size: 10rem; overflow: auto;"> <div style="inline-size: 20rem"> I'm overflowing </div> </div> - The viewport of a document that has a non-null browsing context and is not inert.
For example, the contents of an iframe. Note: Key events routed to an iframe get immediately routed to its active document. - Any other element or part of an element determined by the user agent to be a focusable area, especially to aid with accessibility or to better match platform conventions.
Examples
The element's tabindex value is non-null:
<section tabindex="-1" aria-label="Results">
</section>
The user agent determines the element to be focusable:
<dialog></dialog>
The element has a shadow root whose delegates focus is false
A normally focusable element with a shadow root won't receive focus if it delegates it. In the following example you can't focus the-button even though it has tabindex set to 0 because it delegates focus to the nested button.
html
<the-button tabindex="0">
This is my button
</the-button>
CSS
the-button {
border: 4px solid;
padding: 1rem;
}
JS
class TheButton extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: "open",
delegatesFocus: true
});
const button = document.createElement("button");
button.textContent = "Focus me";
this.shadowRoot.append(button);
}
}
customElements.define("the-button", TheButton);
The element is not actually disabled
That means that the disabled attribute is not present on the element.
<button disabled>This button is actually disabled</button>
The element is not inert
That means that the inert attribute is not present on the element.
<button inert>This button is inert</button>
The element is being rendered
That means that the element has any associated CSS layout boxes
<button hidden>This button is not being rendered</button>
<button style="display: none">This button is not being rendered</button>
The element is delegating its rendering to its children
That means that the element is not being rendered but its children could be rendered.
<div style="display: contents">
This div is not being rendered, but…
<button>…this button is being rendered</button>
</div>
Focusable elements
Most of the things I wrote in the previous chapter probably weren't new to you. You already know that interactive elements are focusable, but what does focusable mean?
An element is focusable when it can be focused programmatically, e.g. via the focus() method or the autofocus attribute.
Focusable elements can either be sequentially focusable, click focusable, both, or none of them. These two terms determine how the user agent responds to user interaction.
Sequentially focusable
Users can reach sequentially focusable elements by pressing the Tab key.
With respect to platform conventions, the specification suggests that the following elements should be sequentially focusable:
<a>that have an href attribute<button><input>with the exception of<input type="hidden"><select><textarea><summary>that are the first summary element child of a details element- Editing hosts (A
[contenteditable]element or a child HTML element of a Document whose design mode enabled is true). - Navigable containers (e.g.
<iframe>) - Elements with a
[tabindex]0 or higher.
Instead of referring to these elements as sequentially focusable, you can also say tabbable.
Click focusable
Users can reach click-focusable elements by clicking them.
HTML:
<button>button</button>
<dialog open>dialog</dialog>
<div tabindex="-1">tabindex=-1</div>
CSS:
:focus {
background: aqua;
}
Click/tab on one of the following elements to focus it.
The button is sequentially and click focusable.
The open dialog is only click focusable in Firefox (144) but also sequentially focusable in Chrome 141 and Safari 18.6 (Interesting!)
The div with the negative tabindex is only click focusable.
That means that an element can be focusable without being sequentially focusable/tabbable.
Neither sequentially focusable nor click focusable.
To make it even more complex, an element can still be programmatically focusable without being sequentially focusable or click focusable.
You can't focus this input field by using Tab or clicking on it, but you can by clicking on the label or the focus button.
HTML:
<label for="r">Read only</label>
<input type="text" id="r" tabindex="-1" readonly>
CSS:
input {
pointer-events: none;
}
JS:
document.querySelector('button').addEventListener('click', () => {
document.querySelector('input').focus();
})
Note: Read-only input elements are tabbable by default, I only added the tabindex attribute for the purpose of this demo.
Making non-interactive elements interactive
There is one more unanswered question: Is it okay to make non-interactive elements interactive?
Here's what MDN has to say about it on the page about the tabindex attribute:
Avoid using the tabindex attribute in conjunction with non-interactive content to make something intended to be interactive focusable by keyboard input.
Considering their wrong definition of the term interactive element, this sentence doesn't make sense because it means that you're only supposed to put the tabindex attribute on sequentially focusable elements. We're not supposed to use -1 or a number greater than 0, so we're left with 0, which does not affect an already focusable element.
It is perfectly acceptable to place tabindex=-1 on a non-interactive element when it helps with accessibility. In a client-side rendered application, I may want to move users to a specific location within the page. For example, in a search widget, I could place the focus on the container that contains all search results after the user has clicked the search button.
<section tabindex="-1" aria-labelledbyy="heading">
<h2 id="heading">42 search results</h2>
…
</section>
The next paragraph starts with this sentence:
Interactive components authored using non-interactive elements are not listed in the accessibility tree.
This sentence isn't correct, but I know what they're trying to say: Don't set tabindex on generic elements. I agree with that. If you use tabindex, use it on a labelled semantic element like I did in the previous example. Here's another example.
<h2 tabindex="-1">42 search results</h2>
Doing something like that is fine, because it makes the element programmatically focusable but not sequentially focusable. In most cases, you shouldn't turn non-interactive elements into sequentially focusable elements. In other words: Don't make a div or h2 tabbable, use a button instead.
tl;dr
Okay, wow, that escalated a bit.
Long story short: Interactive elements are focusable, but focusable doesn't necessarily mean tabbable because there are also click focusable elements, which aren't tabbable but can be focused programmatically or via click.
Also, it's perfectly acceptable to place tabindex on a non-interactive element when it helps with accessibility.