Skip to content
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

Multiple tab stops inside collection components #7121

Open
nwidynski opened this issue Sep 30, 2024 · 15 comments
Open

Multiple tab stops inside collection components #7121

nwidynski opened this issue Sep 30, 2024 · 15 comments
Labels
enhancement New feature or request

Comments

@nwidynski
Copy link
Contributor

nwidynski commented Sep 30, 2024

Provide a general summary of the feature here

We are developing a rather typical e-commerce storefront with interactive product cards, recommendation carousels and facet filtering based on a custom component library on top of react-aria hooks.

In our latest design phase our team has done extensive work on an accessible <Carousel /> & <Accordion /> component leveraging react-aria's <Virtualizer /> and new collection package. These components were designed to become the base on top of which to build our <Filter />, <CardCarousel /> & <CardGrid /> components, all of which represent collection components with unknown, complex, focusable content inside each item.

As we retrospect, we would like to report on obstacles we faced while utilizing existing hooks and subsequently collect feedback on an extension to useSelectableCollection() we could see introduced into react-aria.

Since we know this hook lies at the core of react-aria we decided to open this issue to discuss potential approaches to making react-aria more suitable for complex interactive collections.

For the purpose of this issue, we will mainly narrow on focus behavior and tab sequence of collection components, hoping to gain insights into the decision process and exact accessibility requirements the Spectrum team has for collections.

🤔 Expected Behavior?

While inside the cell, tab, shift+tab and arrow keys can be used to navigate the cell content just like inside a <TabPanel />, fully contained inside the cell, not propagated and storing the last visited focusable element. A click on the cell would remain to set the focused key of the outer collection.

We would love to discuss the background in making collections single tab stop and the feasibility of integrating the proposal into the current state of react-aria, as we would save some bandwith by re-using existing hooks.

😯 Current Behavior

While implementing our use case, we noticed each focusable element inside a cell has to be stepped through to reach the next cell. When discovering a product grid, this presents rather bad UX in our opinion, calling for a way to skip to the next cell.

The issue is worsened by a loss of focus scope when leaving the cell, as the returning focus is placed on the first focusable element inside the cell rather than the last visited. Additionally, arrowup or arrowdown interactions in nested components are not discarded in the cell navigation, causing a jump in the row instead of proper interaction.

💁 Possible Solution

The likely obvious thought would be to add a new keyboard combination to jump to the next cell, but we found it to be rather infeasible as most sensible combinations are already taken up by basic cell navigation or (multi) selection of cells.

In order to solve all issues, we would like to discuss an alternative navigation mode: multi tab stop

In a similar way to the <Tabs /> component, a cell content would pose as the tab panel. While focus is on a cell, arrow key navigation can be used to move between cells. When the desired cell is reached, tab moves the focus inside the cell or outside the collection, depending on whether the cell contains a focusable element.

🔦 Context

Out of the box, react-aria collections can operate in two tab sequences: tab navigation & single tab stop

This design works wonderfully in most scenarios, but becomes rather unhandy when dealing with cells containing a large amount of focusable elements. Real world applications of such use-cases can often be seen in modern project management apps, but also in e-commerce applications, both notoriously troubled with bad accessibility.

💻 Examples

No response

🧢 Your Company/Team

No response

🕷 Tracking Issue

No response

@snowystinger
Copy link
Member

Thanks for the feedback. I can provide the starting information: What patterns are we currently supporting? Each component and hook page in our docs generally has a link to the Aria pattern we have implemented.

For tables, that's data grid
Grid (Interactive Tabular Data and Layout Containers) Pattern
Heading: Keyboard Interaction - Setting Focus and Navigating Inside Cells
Note, we have not implemented edit mode yet.

@devongovett
Copy link
Member

I think the allowsTabNavigation option works as you are describing? You can use tab to move to focusable elements within a cell (and eventually out of the whole collection), and arrow keys to move between cells.

@nwidynski
Copy link
Contributor Author

nwidynski commented Oct 1, 2024

Thanks for the feedback. I can provide the starting information: What patterns are we currently supporting? Each component and hook page in our docs generally has a link to the Aria pattern we have implemented.

For tables, that's data grid Grid (Interactive Tabular Data and Layout Containers) Pattern Heading: Keyboard Interaction - Setting Focus and Navigating Inside Cells Note, we have not implemented edit mode yet.

@snowystinger Thanks for referencing the grid pattern here. Our team only looked at the appropriate patterns of Accordion and Carousel, so we totally missed that there is an edit mode applied not only for input content but all complex content. This mode seems to be what we are looking for!

We implemented both <Accordion />, <CardView /> and <Carousel /> with the behavior of edit mode, only with Tab and Shift+Tab as the entering/exiting key.

I figure the focus is meant to be restored accordingly when re-entering the cell? Any idea how interfering Esc key events would be treated? Enter might also interfere with selection of the cell (e.g multiselect card view).

Are there concrete plans or API spec to get this mode into react-aria? We could maybe provide a draft here after adjusting our keyboard control slightly and assessing how much work it is to port everything to react-aria naming and style.

@nwidynski
Copy link
Contributor Author

nwidynski commented Oct 1, 2024

I think the allowsTabNavigation option works as you are describing? You can use tab to move to focusable elements within a cell (and eventually out of the whole collection), and arrow keys to move between cells.

@devongovett Unfortunately allowsTabNavigation doesn’t do the trick for us since it wasn’t designed to deal with interfering arrow navigation in nested children and doesn’t restore focus on the last focusable child within the cell.

On this note, I have a question: In case edit mode is added to useSelectableCollection(), would an Accordion or Carousel be using this mode?

@LFDanLu
Copy link
Member

LFDanLu commented Oct 4, 2024

Unfortunately allowsTabNavigation doesn’t do the trick for us since it wasn’t designed to deal with interfering arrow navigation in nested children and doesn’t restore focus on the last focusable child within the cell.

Do you happen to have a example/reproduction you can share with us? The team is having a bit of a hard time understanding the exact behavior here.

On this note, I have a question: In case edit mode is added to useSelectibleCollection(), would an Accordion or Carousel be using this mode?

Accordion and Carousel would not use edit mode I imagine, were you imagining a case where those components would use the arrow keys to cycle through the accordions/carousel view thus interfering with things like textfields within those components?

@nwidynski
Copy link
Contributor Author

nwidynski commented Oct 4, 2024

@LFDanLu I just hacked together a small demo. You can also check out Nike or Adidas to verify that those layouts are pretty common in e-commerce.

The <Card /> component is representative for any complex content with arrow navigation. I was asking for <Accordion /> and <Carousel /> because both are collection components in our codebase, and are meant to house complex content (e.g. filters, recommendations, etc.). You will notice the following issues in the demo:

  1. Using arrowup & arrowdown on nested children will bubble up to the container, moving the cell focus
  2. When re-entering the collection, focus is not restored to the last focused element but to the last focused cell.

What would be required, and what we implemented, is a way to "enter" the cell causing navigation listeners to pause on the parent container until exited. In our understanding, this is what edit mode refers to.

While navigation keys, such as arrow keys, are moving focus from cell to cell, they are not available to perform actions like operate a combobox or move an editing caret inside of a cell. The user may need keys that are used for grid navigation to operate elements inside a cell if a cell contains:

  • Editable content.
  • Multiple widgets.
  • A widget that utilizes arrow keys in its interaction model, such as a radio group or slider.

What i was asking @snowystinger about was handler collisions with the Esc key and cell selection when using Enter to enter the cell in edit mode (currently an issue on our Accordion). Sorry if this was too much info at once 😅!

Unfortunately, to implement edit mode behavior, we had to roll our own useSelectableCollection() and we are reaching out to ask the team how we can help to get this worked in, because we waste bandwidth on shipping behavior twice and we could likely benefit a big target audience.

@LFDanLu
Copy link
Member

LFDanLu commented Oct 4, 2024

Gotcha, thank you for the demo. Last I remember from our work on edit mode (which "paused" all the grid navigation listeners when the user pressed Enter and renabled them when hitting Esc) was that we felt it was fairly non intuitive and it wasn't very clear to the user when they were in edit mode or not. It also ran into the issues that you mentioned with regards to collisions between Esc/Enter (e.g. if you were interacting with a searchfield where Esc clears the field, would the user know that you would need to hit Esc again to exit edit mode?). I believe @devongovett has worked on a second iteration that behaved differently, but I'm not sure of the details myself.

As for Accordion and Carousel, it looks like arrow key navigation is optional in the aria pattern for Accordion and not covered for Carousel hence my original stance that it those wouldn't need edit mode. I'm not sure if we would implement them with collection behavior like support on our end but I agree that they will need edit mode support if we did want to go down that route.

I think what you've described in the "Possible Solutions" sound promising, but will have to mull it over/play around with an implementation. I assume you can move from inside the cell back to focusing the entire cell, thus restoring all the collection event handlers (aka row selection and cell navigation via arrow keys would be enabled again?)

@nwidynski
Copy link
Contributor Author

nwidynski commented Oct 5, 2024

I assume you can move from inside the cell back to focusing the entire cell, thus restoring all the collection event handlers (aka row selection and cell navigation via arrow keys would be enabled again?)

@LFDanLu Yes that’s exactly right 👍

Although there remain small UX issues in communicating where the focus will move when trying to enter the cell. The cell might not contain focusable content, thus landing either inside the cell or outside the collection.

While we could ensure a focusable panel, it would be visually identical to the cell. We decided on the Tab key since its pretty much guarantueed to not cause any collisions and is pretty intuitive.

@LFDanLu
Copy link
Member

LFDanLu commented Oct 11, 2024

Talked with the team today and the consensus was that we'd be interested in supporting something like this within useSelectableCollection but we'll need to play around with it some more. Roughly we imagine the flow to be something like so:

  • Arrow keys to navigate between cells
  • use Tab to enter a cell to focus the contents. Tabbing once more when you are at the last focusable element in the cell would tab you out of the collection component
  • Shift + Tab/Escape would return focus to the cell allowing you to once more use the arrow keys to navigate between the cells
  • As for handling events and preventing key navigation from happening when you are inside the cell, we were thinking about stopping event propagation or something by default in textfields/etc but the capturing nature of some of the listeners in the hooks will have to be tweaked, still up in the air about this one

We had a question about your implementation: how do you exit back to focusing the cell when you are inside it? I had assumed Tab handles entering/exiting the cell but someone pointed out that this would be a focus trap hence why our theoretical flow uses Shift + Tab/Escape specifically to exit back to the cell level.

As for Accordion, the interactive elements that would use arrow keys/other interactions would theoretically be in the Accordion content rather than the Accordion header so thus arrow key events won't move focus through the Accordion so no need for edit mode or something like that theoretically. Carousel is still up in the air since we haven't implemented it yet

@nwidynski
Copy link
Contributor Author

nwidynski commented Oct 11, 2024

@LFDanLu Sounds great 👍 Let us know how we can help!

To answer your question, it behaves as your spec describes:

We implemented both <Accordion />, <CardView /> and <Carousel /> with the behavior of edit mode, only with Tab and Shift+Tab as the entering/exiting key.

Shift+Tab walks you back in order therefore bringing you to the last focusable element of the cell when you re-enter the collection from the bottom. It restores to the cell when previously on the first focusable element.

Collisions only occur because of the Esc key interaction, right? This interaction we do not support currently, but i understand the need for a shortcut to exit the cell regardless of where your current focus is.

In regards to <Accordion />:

[…] the interactive elements that would use arrow keys/other interactions would theoretically be in the Accordion content rather than the Accordion header […]

A lot of e-commerce designs often represent active filters as a selectable <TagGroup /> placed inside the <h3 /> while the <DisclosurePanel /> is collapsed.

The complexity of a dynamic filter is also why we implemented <Accordion /> as a collection component. When facets are narrowed they might remove other facets from the filter and we need to move focus accordingly, all of which we get for free in a collection.

No support for adjecent elements inside the header is the tradeoff we took for it. Instead, as you mentioned, we had to accomodate the <TagGroup /> design inside the <DisclosurePanel />, which was a challenge in itself to display while collapsed.

@LFDanLu
Copy link
Member

LFDanLu commented Oct 11, 2024

@nwidynski Thanks for the detailed reply! The details about your Accordion implementation as a collection component is very interesting, at the moment our own Accordion component isn't a collection component but I'm not sure we've considered the focus challenges you outlined. Just to clarify, could you expand some more on these focus scenarios? I was imagining that you ran into focus being lost when a tag was removed that the user was currently focused on but shouldn't the TagGroup handle that? Or what it that an Accordion header itself may be removed and thus focus needs to move else where inside the Accordion as a whole instead?

@nwidynski
Copy link
Contributor Author

@LFDanLu Yep, we dynamically remove the entire<AccordionItem /> out of the collection when the facet cant yield any result.

You can imagine the following scenario:

Specific items may have unique facets. When a user narrows a common facet (e.g category), those items may not appear in the result set. We can subsequently auto-remove the unique facet out of the<Accordion />.

Changes to the result set can happen through user interaction but also programmatically as updates are streamed in.

@patrickkuhlmann
Copy link
Contributor

I made a small recording of it
https://github.com/user-attachments/assets/0566b7db-0af4-4499-9321-1b4d3393d337

@patrickkuhlmann
Copy link
Contributor

Hey hey,

I spent some of my time updating the selectable item / collection hooks what do you think about it? #7215

@LFDanLu
Copy link
Member

LFDanLu commented Oct 21, 2024

Thanks! I'll see if I or someone on the team can take a look soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: 🏗 In Progress
Development

No branches or pull requests

5 participants