From c2b8253f1eab5db1a2a4c98818d7921df1df989a Mon Sep 17 00:00:00 2001 From: Nandor_Czegledi Date: Tue, 16 Dec 2025 15:08:52 +0100 Subject: [PATCH] feat(ui-tabs): add tabIndex prop to the Panel for WCAG-compliant focus control (defaults to 0 for backward compatibility) --- .../src/Tabs/Panel/__tests__/Panel.test.tsx | 22 +++ packages/ui-tabs/src/Tabs/Panel/index.tsx | 3 +- packages/ui-tabs/src/Tabs/Panel/props.ts | 8 +- packages/ui-tabs/src/Tabs/README.md | 148 +++++++++++++++--- 4 files changed, 159 insertions(+), 22 deletions(-) diff --git a/packages/ui-tabs/src/Tabs/Panel/__tests__/Panel.test.tsx b/packages/ui-tabs/src/Tabs/Panel/__tests__/Panel.test.tsx index d79b29e65d..38cec62175 100644 --- a/packages/ui-tabs/src/Tabs/Panel/__tests__/Panel.test.tsx +++ b/packages/ui-tabs/src/Tabs/Panel/__tests__/Panel.test.tsx @@ -49,4 +49,26 @@ describe('', () => { expect(tabPanel).toHaveAttribute('role', 'tabpanel') }) + + it('should not have tabIndex 0 by default', async () => { + const { container } = render( + + Panel contents + + ) + const tabPanel = container.querySelector('[role="tabpanel"]') + + expect(tabPanel).not.toHaveAttribute('tabIndex', '0') + }) + + it('should allow custom tabIndex', async () => { + const { container } = render( + + Panel contents + + ) + const tabPanel = container.querySelector('[role="tabpanel"]') + + expect(tabPanel).toHaveAttribute('tabIndex', '-1') + }) }) diff --git a/packages/ui-tabs/src/Tabs/Panel/index.tsx b/packages/ui-tabs/src/Tabs/Panel/index.tsx index 1d8361a2e0..1ad95c33c3 100644 --- a/packages/ui-tabs/src/Tabs/Panel/index.tsx +++ b/packages/ui-tabs/src/Tabs/Panel/index.tsx @@ -98,6 +98,7 @@ class Panel extends Component { styles, active, unmountOnExit, + tabIndex, ...props } = this.props @@ -106,10 +107,10 @@ class Panel extends Component { {...passthroughProps(props)} css={styles?.panel} role="tabpanel" - tabIndex={0} id={id} aria-labelledby={labelledBy} aria-hidden={this.isHidden ? 'true' : undefined} + tabIndex={this.isHidden ? undefined : tabIndex} ref={this.handleRef} > { > { - {lorem.paragraphs()} + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + velit esse cillum dolore eu fugiat nulla pariatur. - {lorem.paragraphs()} + Sed ut perspiciatis unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium. Totam rem aperiam, eaque ipsa quae ab + illo inventore veritatis et quasi architecto beatae vitae dicta sunt + explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut + odit aut fugit, sed quia consequuntur magni dolores. - {lorem.paragraphs()} + At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis + praesentium voluptatum deleniti atque corrupti. Quos dolores et quas + molestias excepturi sint occaecati cupiditate non provident, similique + sunt in culpa. Qui officia deserunt mollitia animi, id est laborum et + dolorum fuga. ) @@ -74,17 +89,29 @@ const Example = () => { minHeight="10rem" maxHeight="10rem" > - + Hello World - {lorem.paragraphs()} + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + velit esse cillum dolore eu fugiat nulla pariatur. - - {lorem.paragraphs()} + + Sed ut perspiciatis unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium. Totam rem aperiam, eaque ipsa quae ab + illo inventore veritatis et quasi architecto beatae vitae dicta sunt + explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut + odit aut fugit, sed quia consequuntur magni dolores. - - {lorem.paragraphs()} + + At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis + praesentium voluptatum deleniti atque corrupti. Quos dolores et quas + molestias excepturi sint occaecati cupiditate non provident, similique + sunt in culpa. Qui officia deserunt mollitia animi, id est laborum et + dolorum fuga. ) @@ -122,50 +149,57 @@ const Example = () => { id="tabA" renderTitle="Tab A" isSelected={selectedIndex === 0} + tabIndex={0} > - {lorem.sentence()} + Lorem ipsum dolor sit amet, consectetur adipiscing elit. - {lorem.sentence()} + Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - {lorem.sentence()} + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. - {lorem.sentence()} + Duis aute irure dolor in reprehenderit in voluptate velit esse. - {lorem.sentence()} + Excepteur sint occaecat cupidatat non proident, sunt in culpa. - {lorem.sentence()} + Sed ut perspiciatis unde omnis iste natus error sit voluptatem. - {lorem.sentence()} + Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit. ) @@ -244,6 +278,7 @@ const Example = () => { > { - {lorem.paragraphs()} + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim + ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. - {lorem.paragraphs()} + Sed ut perspiciatis unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium. Totam rem aperiam, eaque ipsa quae + ab illo inventore veritatis et quasi architecto beatae vitae dicta + sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + aspernatur aut odit aut fugit, sed quia consequuntur magni dolores. - {lorem.paragraphs()} + At vero eos et accusamus et iusto odio dignissimos ducimus qui + blanditiis praesentium voluptatum deleniti atque corrupti. Quos + dolores et quas molestias excepturi sint occaecati cupiditate non + provident, similique sunt in culpa. Qui officia deserunt mollitia + animi, id est laborum et dolorum fuga. @@ -299,7 +349,14 @@ const Outlet = () => { {show ? 'Hello Developer' : 'Simulating network call...'} {show ? ( - lorem.paragraphs() +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad + minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. +
) : ( )} @@ -327,6 +384,7 @@ const Example = () => { padding="large" isSelected={selectedIndex === 0} active + tabIndex={0} >
@@ -387,6 +445,7 @@ const Example = () => { > { { id="tabC" renderTitle="Tab C" isSelected={selectedIndex === 2} + tabIndex={0} > Tab C @@ -415,6 +476,7 @@ const Example = () => { id="tabD" renderTitle="Tab D" isSelected={selectedIndex === 3} + tabIndex={0} > Tab D
@@ -425,6 +487,52 @@ const Example = () => { render() ``` +### Managing focus with tabIndex + +**Best practice:** For text-only panels, set `tabIndex={0}` to include the panel in the keyboard tab sequence—this ensures screen reader users can navigate to and read the content. For panels containing interactive elements (buttons, inputs, links), leave `tabIndex` unset so keyboard users tab directly to the controls without stopping on the panel container first. + +```js +--- +type: example +--- +const Example = () => { + const [selectedIndex, setSelectedIndex] = useState(0) + + const handleTabChange = (event, { index }) => { + setSelectedIndex(index) + } + + return ( + + + + + + This panel only contains text, so tabIndex is set to 0 to include it in the tab sequence. + + + ) +} + +render() +``` + ### Guidelines ```js