-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Tabs: improve focus behavior #55287
Tabs: improve focus behavior #55287
Conversation
@chad1008 Setting this prop to true will likely have unattended consequences in trunk. I am pretty sure my E2E test should catch any failures but need to see how this behaves around my custom list view focus management code in edit-post/edit-site. |
There shouldn't be any issues on trunk for now @alexstine 🙂 This new prop is isolated to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tests well in SB when going through your detailed instructions! 🎉
One note - the story reads a bit like a unit test to me. Since focusable
is set per panel, perhaps instead it could be demoed in the default story by mentioning it in the tab panel content? Then have a unit test to test the behaviors you've outlined in your steps.
@alexstine , to add some more context to the work being done here:
This PR simply adds extra control for consumers of |
Thanks for that @brookewp! Somehow I just totally spaced on unit tests, so I've added a couple now. Also liked your idea of demoing this prop in the default, so the new story is gone and |
Flaky tests detected in 096f66a. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6786306372
|
|
||
// By default the tabpanel should receive focus | ||
await user.keyboard( '[Tab]' ); | ||
expect( selectedTabPanel ).toHaveFocus(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In your testing instructions, you have the following step:
While the tabpanel is focused, inspect the element and confirm the component's new :focus-visible style is overriding the browser defaults.
Since you mentioned it in your testing instructions, maybe it would be worth including in the test? Something like:
expect( selectedTabPanel ).toHaveAttribute( 'data-focus-visible' );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tabpanel
element receiving :focus-visible
isn't new in this case, so I don't want to add a test for it. The change around :focus-visible
for this PR is that when the tab-panel
gains that attribute, it gets styled by our new CSS, rather than the browser defaults, which is what was styling it previously :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for clarifying! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you run into any flaky test behavior? When watching the tests, sometimes I'll get a random test failing. For an example:
● Tabs › Uncontrolled mode › Disabled tab › should disable the tab when `disabled` is `true`
expect(element).toHaveFocus()
Expected element with focus:
<button aria-controls="tabs-21-delta-view" aria-disabled="true" aria-selected="false" class="delta-class css-1v2gegi-Tab enfox0g1" data-command="" id="tabs-21-delta" role="tab" tabindex="-1" type="button">Delta</button>
Received element with focus:
<button aria-controls="tabs-21-alpha-view" aria-selected="true" class="alpha-class css-1v2gegi-Tab enfox0g1" data-active-item="" data-command="" data-focus-visible="" id="tabs-21-alpha" role="tab" type="button">Alpha</button>
It'll pass the next 4-5 times then fail again. I'm wondering if it's something related to past flaky test issues we've enountered in the ariakit migrations: #52133 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. If I run them a bunch in a row, I do see that. 🤔 Took me probably close to 20 runs to trigger it, but there it is. I can actually see two tests doing it:
Tabs › Uncontrolled mode › Disabled tab › should disable the tab when
disabledis
true`Tabs › Tab Activation › should not focus the next tab when the Tab key is pressed
Maybe it's related to that other issue you mentioned, but if it is, the same fix isn't helping us here... one of those failures is actually already using findByRole
.
I'll note this to investigate in a followup, that way if we deploy something to fix it, it isn't wrapped up in this change!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For tooltip, we added a helper function to ensure everything was resolved before moving to the next test. So I wonder if it would help to add another user.tab()
at the end of each test (or to specific tests).
I'll note this to investigate in a followup, that way if we deploy something to fix it, it isn't wrapped up in this change!
Good plan!
@@ -139,4 +139,12 @@ export type TabPanelProps = { | |||
* Custom CSS styles for the rendered `TabPanel` component. | |||
*/ | |||
style?: React.CSSProperties; | |||
/** | |||
* Determines whether or not the tabpanel element should be focusable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I made one small change, but I didn't update the style
JSDoc because that one's going away in a separate PR merging soon, and changing it here would have just been asking for a merge conflict to reconcile 😆
ec47c37
to
e779ff9
Compare
Thanks for the eyes @brookewp! I've responded to your feedback, added a CHANGELOG, and rebased. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! 🎉
Just have the one changelog conflict to address... 😅
Note
We're talking about
Tabs
, which is a new version ofTabPanel
. There are subcomponents whose names involve the word 'Tab' or 'TabPanel'. There are actual elements withtab
andtabpanel
roles. To top it all off, this PR focuses on the behavior of the [Tab] key on the keyboard. Basically there are a lot of ways to use the word "tab" here, so let me know if anything fails to make sense 😉What?
Add focus styles and a new prop to Tabs.TabPanel to declare whether or not the
tabpanel
itself should be focusable.Why?
The relevant ARIA guidance for the Tabs Pattern states:
How?
Currently, the [Tab] key moves focus from the
tablist
to the currently displayedtabpanel
, regardless of the presence of focusable child elements within thattabpanel
.We've decided to maintain the existing behavior as the default to remain consistent with the behavior of other components (
Modal
, for example). Along with that, this PR exposes afocusable
prop on theTabs.TabPanel
subcomponent to better control this behavior.The new prop will default to
true
, making thetabpanel
focusable, so it will be the next tabstop when using the [Tab] key to navigate off of thetablist
. When the new prop isfalse
, thetabpanel
is no longer focusable. Focus will instead jump to the first focusable element in the DOM. That could be a button or input within thetabpanel
, or if none are present, whatever the next tabstop is elsewhere on the page. We aren't targeting any specific elements to focus, as we don't have a reliable means of determining which elements contain "meaningful content" from one implementation to the next.While this doesn't perfectly align with the guidance described above, it feels like a good compromise of consistency with other components and flexibility for consumers.
Testing Instructions
Tab
. You may need to press it twice to cycle over Storybook's "Return to sidebar" button.Tab
has focus (it will gain an outline), press the [Tab] key again, and confirm that the focus moves to thetabpanel
element.tabpanel
is focused, inspect the element and confirm the component's new:focus-visible
style is overriding the browser defaults.Tab
s within thetablist
Tab
, confirm that thetabpanel
does not get focus. The focus should instead cycle through any focusable elements within the TabPanel contents, then to the button below (and outside of) the component.tabpanel
.