How to make a custom web component focusable?
@Denilson, I would like to provide you with some more information.
As you said, this.tabIndex = 0
works when your webcomponent contains no focusable elements. If it does, it gets more complicated.
For example, if your component contains one or more inputs, then first the "whole" component gets focus, and only later, when tabbing, each inner inputs get focus, one by one. This is usually not what you want. Usually, when the component gets focus this should mean its first input gets focus immediately.
Also, there is a reverse tabbing problem. If your first input has focus and you press SHIFT-TAB, then the "whole" component gets focus, and you are forced to press SHIFT-TAB twice to move to the previous element.
I found this to solve all focus and tabbing problems:
// At first, the component may get focus and accept tabbing.
createdCallback = function () { this.tabIndex = 0; }
// When the component gets focus, pass focus to the first inner element.
// Then make tabindex -1 so that the component may still get focus, but does NOT accept tabbing.
focus = function (e) { firstFocusableInnerElement.focus(); this.tabIndex = -1; }
// When we completely left the component, then component may accept tabbing again.
blur = function (e) { this.tabIndex = 0; }
Note: As of now (Sep 2015) if an inner element gets focus, then the "whole" element is not matched by the :focus
pseudo-selector (tested only in Chrome). If find this behavior to be just plain wrong. The focus event was fired, and the blur event was not. So the element should have focus, right? I hope they change this in the future.
Short answer: delegatesFocus
is what you need here, not tabindex
.
Details:
Assuming that you have interactive elements inside the shadow DOM, there is no satisfying way to make the component programmatically focusable with tabindex
:
- if you set it to
0
you add the host element to the tab sequence ("sequential keyboard navigation") and you have an extra tab stop - if you set it to
-1
you remove not only the host element but any interactive element inside its shadow DOM from the tab sequence, so the whole thing becomes inaccessible for keyboard users
There's a web component API just for this: ShadowRoot.delegatesFocus
, see here. Set this to true
and you'll get:
- calling
.focus()
on the host or clicking on any non focusable part of the component focuses the first focusable element in the shadow DOM :focus
styles are applied to the host in addition to the focused element within- tab sequence is unchanged (it should already work the way you want)
It's supported since shadow DOM v1.
Based on this demo that I found in this question, I have this answer:
Just add the tabindex
attribute to the elements you want to be focusable.
// Add this to createdCallback function:
if (!this.hasAttribute('tabindex')) {
// Choose one of the following lines (but not both):
this.setAttribute('tabindex', 0);
this.tabIndex = 0;
}
// The browser automatically syncs tabindex attribute with .tabIndex property.
Clicking on the element will give it focus. Pressing tab will work. Using :focus
in CSS will also work. keydown
and keyup
events work, although keypress
doesn't (but it's deprecated anyway). Tested on Chrome 44 and Firefox 40.
Also note that this.tabIndex
returns -1
even if the HTML attribute is missing, but this has a different behavior than setting tabindex="1"
:
<foo></foo>
: Notabindex
attribute, the element is not focusable.<foo tabindex="-1"></foo>
: The element is not reachable through tab-navigation, but it is still focusable by clicking.
References:
- http://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute
- https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute
- https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
- https://github.com/whatwg/html/issues/113