-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for determining which elements are focusable and tabable #2071
Comments
In general this seems pretty reasonable to me, although using a selector makes this a request for the CSSWG instead of the HTML Standard. But we can certainly have the discussion here. @tabatkins, @TakayoshiKochi, what do you think? |
This is an excellent problem statement. Probably we can find lots of issues to be solved starting from this. One of the complications is that an element can be focusable but not tabable (i.e. If we had a fictitious pseudo class One concern for implementation is, that Blink has (and maybe others have) an internal function to determine whether an element is focusable or not, but it depends on style/layout is complete so exposing the function to web may result in entangled dependency. |
I think we might be able to simplify it a bit. If we did have a fictitious pseudo class (or node filter for that matter), it would aways start at a root node looking for nodes under it that are focusable and tabable. Therefore, we can ignore all nodes outside of the root since we're only concerned with the roots decedents, even if they would theoretically be above the root in focus order. I would expect the returned NodeList to be in focus order, so that grabbing the first/last index from the list would be the first and last focusable and tabable element from the root. So visual order / true DOM order isn't important (though the DOM order does determine focus order). I wonder if for this reason a query selector isn't the best interface since query selectors usually return elements in DOM order. So if this were my DOM: <dialog>
<button>Close</button>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
<input type="hidden">Secret</input>
<button tabindex="1">Send</button>
<button style="float: left">Cancel</button>
</dialog>
<main>
<button tabindex="10">
</main> Then performing the querySelector on
Essentially it would require walking the DOM, verifying that the first element is tabable (which each UA could use their own heuristic), then if the element is focusable (again, each UA's own heuristic), then inserting the DOM into the returned list in focus order (taking into account tabindex > 0). |
Yes, we definitely would not use querySelector if you had specific ordering requirements; we'd need a new API. But as @TakayoshiKochi says, determining that order is very expensive. Probably better would be to emulate similar primitives to what browsers have, i.e. getNextFocusableArea/getPreviousFocusableArea. Of course there's another problem where the focusable areas are not elements or even nodes. I'm not sure how you'd want to handle that. |
Anything that moves the needle closer to getting this implemented would be better than trying to get it perfect and then be too difficult to implement. If the easiest thing would be to just return a list of all focusable and tabable elements not in tab order, I'd be ok with that. I'm not sure what you mean by that last part. |
The current Blink's implementation does not optimize much about detecting which element is focusable, However at least all browsers implement (internal) functions to detect which element to focus next/prev when TAB/shift+TAB is pressed, so exposing them as For the last part of domenic's #2071 (comment) meant that "a focusable area" may not be a single element, e.g., |
Actually, if For focus areas not being nodes/elements (such as the video element with controls), would it be easy to always return the node/element? |
If the use case here is for modal elements and tab-wrapping, I think #897 is a proposal more fit to the use case. |
The primary use case was for auto-focusing the first focusable element in the dialog. Tab-wrapping was another use case, but #897 does fit better with that one. Maybe the two are related though as declaring a blocking element could use Another use case would be auto-closing a navigation menu when the user tabs off the final focusable element, such as in Heydon Pickering's aria submenu example. If you were trying to build a library for that behavior, having an api to know which elements are focusable would be very helpful. |
Just curious if the |
Tabbable I believe is possible today with var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
{ acceptNode: function(node) { return (node.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP); } },
false
);
var nextFocusableNode = treeWalker.nextNode(); Note that the Programmatic focusability is a whole other issue, though. |
With #1929 changing the modal requirement to focus the dialog instead of the first focusable element (or using |
In my case, I want to detect whether a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus |
Proposal
Add support for determining which elements are focusable and tabable. Probably the most convenient API for developers is through a query selector since we'll need to generate a list of elements. A tree walker node filter would also be acceptable.
Why?
For accessibility reasons this is desperately needed. When implementing an accessible modal it is recommended to auto focus the first focusable element when the modal is opened, as well as trap focus inside the modal.
However, there is no way to determine what elements are focusable. The current "best" way to do this is using this answer from Stack Overflow, which tries to build up a query selector of known focusable elements (even Polymer uses this approach). However, there is a huge flaw to this approach.
(There's also allyjs, but downloading a 20kb minified & gzipped library just to manage focus is a bit overkill. It also uses a known list of focusable elements, so in the end it's not any different then the Stack Overflow answer).
The flaw is that with custom elements, a known list of focusable elements is no longer possible. Take for example this simple custom element.
With the input field, the custom element is now focusable (
document.activeElement
will return thesearch-element
when focus is on the input). This means any custom element could be focusable, making it impossible to use a whitelist of known native elements to determine focusability and tabablility.If we can't reliably use a know list of selectors, that means that the only other way to know what is focusable is to actually test every element in the DOM to see if it moves the
document.activeElement
.Which of course is a terrible idea and will be slower the more elements your site has.
Regardless, custom elements again make this difficult since calling
.focus()
on them doesn't do anything. You could try to see if the element had ashadowRoot
and then traverse it's DOM for focusable elements, but usingattachShadow({mode: closed})
makes that impossible. The only way for custom elements to show that they are focusable is to use the little known delegatesFocus property.In the end, developers have no good way to make an accessible modal without the consumer of the modal marking all focusable elements (or at least, the first and last focusable elements), and ensuring all focusable custom elements use the delegatesFocus proeprty.
The text was updated successfully, but these errors were encountered: