[focusgroup] Should a new attribute be used to manage entry priority rather than tabindex?
Today, an element with tabindex=-1 is never considered to be sequentially focusable, but focusgroup is attempting to change this in order to guarantee a single tab stop. Consider the following cases:
<div focusgroup="toolbar">
<button tabindex=-1>Bold</button>
<button>Italic (recommended)</button>
<button tabindex=-1>Underlined</button>
</div>
In the case above, the author wants the first focus to be on "Second", but since they didn't disable memory, they expect the user's last focused item to be restored when focus returns to the focusgroup, and this memory could be one of the other options. This is something I think we absolutely should support.
<div focusgroup="toolbar">
<button tabindex=-1>Bold</button>
<button tabindex=-1>Italic</button>
<button tabindex=-1>Underlined</button>
</div>
In the case above, we still need to ensure a tabstop in the focusgroup. While this would have been the equivalent to setting them all to "0" (or simply relying on native focusability), because the first example "just works" I think it is reasonable to assume authors would expect this to work as well.
@mfreed7 suggested focusgroup could avoid the need to use tabindex for managing entry priority with the use of a new attribute that would be set on a focusgroup item: focusgroup-initial-focus. I'm typically adverse to scope-creep but I agree with the sentiment that adding an element with tabindex=-1 to the tab order seems unnatural.
With this, the I think following changes would need to be made to the current proposal:
- The "Changes to sequential focus navigation" section would now no longer consider the value of tabindex to factor into the priority calculation.
- Instead,
focusgroup-initial-focuscould be set on any elements that should be prioritized.
- Instead,
- An element with a tabindex=-1 would not be considered a focusgroup item and would not be managed by focusgroup at all.
Here are some examples of usage:
<div focusgroup="toolbar">
<button>Bold</button>
<button focusgroup-initial-focus>Italic (recommended)</button>
<button>Underlined</button>
</div>
With this, authors will actually have cleaner looking html, as they only need to set the attribute on a single element, rather than setting tabindex=-1 on all of the elements that shouldn't be prioritized.
<button>before</button>
<div focusgroup="toolbar no-memory">
<button>Bold</button>
<button focusgroup-initial-focus>Italic (recommended)</button>
<button>Underlined</button>
<button focusgroup-initial-focus>Strikethrough (recommended)</button>
<button>Heading 1</button>
</div>
<button>after</button>
In this example, the author decided they wanted multiple elements to have priority, so when focus is on "before" and the tab key is pressed, focus is moved to "Italic", but when focus is on "after", and shift+tab is pressed, focus is instead set to "Strikethrough".
Since this can be used in cases outside of the initial behavior (e.g. when no-memory is set), I'm thinking focusgroup-entry-priority may be another good name for such an attribute, but I'm curious what others think of the concept, eager to get feedback here.
+1 to this idea. I especially like the fact that tabindex and focusgroup are no longer coupled and that it's way easier to state the intended behaviour by just setting one attribute instead of n-1.
How would an initial focus entry element behave if another candidate had a positive tabindex? If the resolution to https://github.com/openui/open-ui/issues/1311 is positive, that would be a nice solution, otherwise one or the other needs to be prioritised.
Just to clarify the behaviour with no-memory set: Does the initial-focus-entry element effectively become the memory element or does it just receive focus the first time focus enters and the second time the first or last elements get focus? Or is the attribute just ignored in this case?
How would an initial focus entry element behave if another candidate had a positive tabindex? If the resolution to https://github.com/openui/open-ui/issues/1311 is positive, that would be a nice solution, otherwise one or the other needs to be prioritised.
I think this new initial-focus-entry attribute would make #1311 irrelevant, as with this focusgroup would not look at tabindex's value at all when considering entry priority. Of course, an element still needs to be focusable in order to be managed by focusgroup, so they will still use tabindex in some capacity, I would encourage tabindex="0".
Just to clarify the behaviour with no-memory set: Does the initial-focus-entry element effectively become the memory element or does it just receive focus the first time focus enters and the second time the first or last elements get focus? Or is the attribute just ignored in this case?
This would just be for the first time focus enters the focusgroup, memory would absolutely take precedent if there were a last focused item. This entry-selection can also occur when the memory item isn't in the focusgroup segment being tabbed to, when the memory element was removed from the focusgroup, or of course when no-memory is set.
I like this part:
- An element with a tabindex=-1 would not be considered a focusgroup item and would not be managed by focusgroup at all.
If focusgroup is not supported or can't be used because the keyboard lacks the required key, sequential navigation would still work fine, because focusable elements would fall back to being sequentially focusable.
If I’ve got this right: I can add tabindex="-1" to the parent of some focusable elements, and those elements can still be part of a focusgroup.
for example:
<a href="#target" autofocus>go to target</a>
<div focusgroup="toolbar">
<button>button</button>
<button initialfocus>button</button>
<div id="target" tabindex="-1">
<h3>target</h3>
<button>button</button>
</div>
</div>
Currently, I think this can't be done, because focusgroup="none" removes its focusable descendants from arrow traversal:
<a href="#target" autofocus>go to target</a>
<div focusgroup="toolbar">
<button tabindex="-1">button</button>
<button>button</button>
<div id="target" tabindex="-1" focusgroup="none">
<h3>target</h3>
<button>button</button>
</div>
</div>
If I’ve got this right: I can add tabindex="-1" to the parent of some focusable elements, and those elements can still be part of a focusgroup.
I hadn't thought about this, but yes, this would also light up this ability. For absolute clarity, when focus is programmatically set to the "target" div, arrow keys won't do anything, as this is not a "focusgroup item", but tab should move focus to the nested button, similar to how navigation from an opted-out element would work, and from there you can interact with the rest of the focusgroup as normal.
Wouldn't it be more intuitive if Tab moved focus to the next sequentially focusable element, including the Guaranteed Tab Stop?
Wouldn't it be more intuitive if Tab moved focus to the next sequentially focusable element, including the Guaranteed Tab Stop?
That sounds right to me, realizing now that my explanation was ambiguous since all of the buttons were called "button". Let me get into what I am imagining:
<a href="#target" autofocus>go to target</a>
<div focusgroup="toolbar">
<button>button1</button>
<button initialfocus>button2</button>
<div id="target" tabindex="-1">
<h3>target</h3>
<button>button3</button>
</div>
</div>
<button>after focusgroup</button>
- User presses: "go to target"
- Focus is programmatically moved to the div with the id of "target". Here, arrow key's do not interact with focusgroup, as this div is not considered to be a focusgroup item.
- If the user presses "tab", focus is moved to "button3" as this focusgroup segment is conceptually "after" the tabstop on "target".
- Now that focus is on the "button3", which is a focusgroup item, arrow keys can be used to move focus between all focusgroup items.
I like the idea of having a specific attribute a fair amount, especially because it's much easier and feels simpler to read & understand when there's one attribute to set on a single element to signal the initial tab stop vs. using tabindex to remove all other focusable controls (which especially could get weird when you start dynamically adding/removing elements).
I also like that it decouples setting the initial focus stop from the current meaning of tabindex which is more persistently "this should be in the tab/focus order" (which I think is what you were essentially saying in the issue description).
I'm a little less sure I understand the idea of having multiple focusgroup-initial-focus elements and evaluating them differently going forward vs. backward. That seems like a straightforward authoring error to me, and should just have one single way to resolve it, regardless of the directly the user is tabbing. Having the tab stop be different when navigating forward vs. backward just seems like a straightforward a11y error, I can't think of a use case for it.
There's also another use case that might be solveable with this approach, and I'm curious about whether you think it's possible: if the author needs to reset the initial focus stop, could they do so by removing focusgroup-initial-focus and re-setting it on a different element, even with memory set? A couple use cases include:
- the currently focused (and thus remembered) element is removed from the DOM, and the author wants to specify which other element should get the default tab next, but keep memory after that.
- Or: new elements have been appended, and the author wants the next tap stop to go to the latest one, then be remembered again after that (e.g. in a chat message list, you'd want to tab first to the latest message after one is added, but remember where you were otherwise)
having the tab stop be different when navigating forward vs. backward just seems like a straightforward a11y error, I can't think of a use case for it.
I don't think different forwards/backwards behavior is unreasonable, consider the following situation:
<button>Before</button>
<div focusgroup="toolbar no-memory">
<button>Bold</button>
<button>Italic (recommended)</button>
<button>Underlined</button>
</div>
<button>After</button>
Consider the following situation: When focus is on "Before" and tab is pressed, "Bold" should be the initially focused item, hitting tab again brings us to "After". If instead focus was on "After" and shift+tab was pressed, "Underlined" should instead be the initially focused item.
That said, the idea of multiple items having focusgroup-initial-focus does seem strange for the above situation, but I can imagine a focusgroup that has multiple segments with opted out (and sequentially focusable) elements in between may want to use more than one.
<div focusgroup="toolbar inline wrap no-memory">
<button focusgroup-initial-focus>Bold</button>
<button>Italic</button>
<span focusgroup="none" aria-label="Help group">
<button>Help</button>
<button>Shortcuts</button>
</span>
<button>Underline</button>
<button focusgroup-initial-focus>Search</button>
<button>Heading 1</button>
<button>Heading 2</button>
</div>
Let me know what you think, I could be convinced that only a single item with focusgroup-initial-focus should be allowed.
There's also another use case that might be solveable with this approach, and I'm curious about whether you think it's possible: if the author needs to reset the initial focus stop, could they do so by removing focusgroup-initial-focus and re-setting it on a different element, even with memory set? A couple use cases include:
- the currently focused (and thus remembered) element is removed from the DOM, and the author wants to specify which other element should get the default tab next, but keep memory after that.
- Or: new elements have been appended, and the author wants the next tap stop to go to the latest one, then be remembered again after that (e.g. in a chat message list, you'd want to tab first to the latest message after one is added, but remember where you were otherwise)
I don't like the idea of this overriding memory, If an author doesn't want the memory behavior, they should use no-memory, I think toggling no-memory on and off depending on the situation might make more sense here.
- For point 1- This is how I currently expect this to work, if the memory element is removed, there is nothing to restore, so this makes sense to me.
- For point 2 - This seems to me like the author conditionally wants memory, in that case I think it might make more sense for them to toggle the "no-memory" field on the focusgroup for when they do and don't want the browser to record/restore the previously focused element.
This, to me, sounds like a known tabster bug and not desired behavior (and is more or less what I'd want to prevent 😅):
When focus is on "Before" and tab is pressed, "Bold" should be the initially focused item, hitting tab again brings us to "After". If instead focus was on "After" and shift+tab was pressed, "Underlined" should instead be the initially focused item.
Is there an app that works like this? I was trying to tab through some native ones, and of the ones that don't just allow tab to move through all items, the default tab stop is the first item in a toolbar regardless of the directly of tabbing.
For context, I've had some incidental user feedback in studies about that tabster bug, so there is some direct evidence that it's confusing / not expected (though I haven't made it a focus of any studies, since I didn't really think there was any disagreement about it not being correct).
I don't like the idea of this overriding memory, If an author doesn't want the memory behavior, they should use no-memory, I think toggling no-memory on and off depending on the situation might make more sense here.
I think toggling memory on/off, or just fully taking control of managing the initial focused element are definitely reasonable alternatives 👍. I had a feeling trying to override memory by changing the attribute might be more fragile / complex than it's worth, but wanted to at least throw the use case out there. Thanks for looking into it!
This, to me, sounds like a known tabster bug and not desired behavior (and is more or less what I'd want to prevent 😅):
When focus is on "Before" and tab is pressed, "Bold" should be the initially focused item, hitting tab again brings us to "After". If instead focus was on "After" and shift+tab was pressed, "Underlined" should instead be the initially focused item.
Is there an app that works like this? I was trying to tab through some native ones, and of the ones that don't just allow tab to move through all items, the default tab stop is the first item in a toolbar regardless of the directly of tabbing.
For context, I've had some incidental user feedback in studies about that tabster bug, so there is some direct evidence that it's confusing / not expected (though I haven't made it a focus of any studies, since I didn't really think there was any disagreement about it not being correct).
It took me a moment to dig up the history, but this is where this idea seems to have come from: https://github.com/openui/open-ui/issues/537#issuecomment-1934930124 Since this isn't strictly related to this new attribute, I created https://github.com/openui/open-ui/issues/1316 so we can talk about the directionality issue in isolation.
For this attribute, I think ensuring that direction doesn't impact the chosen item is reasonable, see below:
How would you feel about:
"focusgroup-initial-focus should be set on the focusgroup item you would like to have priority when entering the focusgroup segment with sequential focus navigation (tab/shift+tab). If an author adds this attribute to multiple focusgroup items in the same segment, priority will always be assigned to the first item with the attribute in DOM order. We do not recommend adding this attribute to multiple focusgroup items in the same segment."
Example 1
<button>Before</button>
<div focusgroup="toolbar no-memory">
<button>Bold</button>
<button focusgroup-initial-focus>Italic (recommended)</button>
<button focusgroup-initial-focus>Underlined (recommended)</button>
</div>
<button>After</button>
When entering the focusgroup from either direction with sequential focus navigation (tab/shift+tab), the "Italic" button will be focused.
Example 2
- Focus is on "Before"
- User presses "Tab", focus moves to "Italic" as it is the first focusgroup item with the
focusgroup-initial-focusattribute set. - User presses "Tab" again, focus moves to "Help" as it is part of an opted out subtree, and is not considered as part of the focusgroup.
- User presses "Tab" again, focus moves to "Search" as it is the first focusgroup item with the
focusgroup-initial-focusattribute set. - User presses "Tab" again, focus moves to "After"
- User presses "Shift tab", focus moves to "Seach" as it is the first focusgroup item with the
focusgroup-initial-focusattribute set. (direction doesn't matter) - User presses "Shift tab", focus moves to "Help"
- User presses "Shift tab", focus moves to "Italic" as it is the first focusgroup item with the
focusgroup-initial-focusattribute set. - User presses "Shift tab", focus moves to "Before"
I feel this gives enough author flexibility and is straightforward to understand. Let me know what you think.
What about the name focusgroup-priority, this is a little shorter while being even clearer IMO
Other name ideas:
- initialfocus
- focusentry
- entryfocus
- focusfirst
- leadfocus
- focuspreference