Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/react-core/src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export interface TabsProps
onAdd?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
/** Aria-label for the add button */
addButtonAriaLabel?: string;
/** A readable string to create an accessible name for the tablist element. This can be used to differentiate multiple tablists on a page, and should be used for subtabs. */
tabListAriaLabel?: string;
/** Id of an element that provides an accessible name for the tablist. Use this when a visible label already exists on the page. */
tabListAriaLabelledBy?: string;
/** Uniquely identifies the tabs */
id?: string;
/** Flag indicating that the add button is disabled when onAdd is passed in */
Expand Down Expand Up @@ -500,6 +504,8 @@ class Tabs extends Component<TabsProps, TabsState> {
toggleText,
toggleAriaLabel,
addButtonAriaLabel,
tabListAriaLabel,
tabListAriaLabelledBy,
onToggle,
onClose,
onAdd,
Expand Down Expand Up @@ -626,7 +632,14 @@ class Tabs extends Component<TabsProps, TabsState> {
/>
</div>
)}
<ul className={css(styles.tabsList)} ref={this.tabList} onScroll={this.handleScrollButtons} role="tablist">
<ul
aria-label={tabListAriaLabel}
aria-labelledby={tabListAriaLabelledBy}
className={css(styles.tabsList)}
ref={this.tabList}
onScroll={this.handleScrollButtons}
role="tablist"
>
{isOverflowHorizontal ? filteredChildrenWithoutOverflow : filteredChildren}
{hasOverflowTab && <OverflowTab overflowingTabs={overflowingTabProps} {...overflowObjectProps} />}
</ul>
Expand Down
48 changes: 48 additions & 0 deletions packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -742,3 +742,51 @@ test(`should render with custom inline style and accent position inline style`,

expect(screen.getByRole('region')).toHaveStyle(`background-color: #12345;--pf-v6-c-tabs--link-accent--start: 0px;`);
});

test('should render tablist aria-label when provided', () => {
const { asFragment } = render(
<Tabs id="tabListLabelTabs" tabListAriaLabel="Primary tab list">
<Tab id="tab1" eventKey={0} title={<TabTitleText>"Tab item 1"</TabTitleText>}>
Tab 1 section
</Tab>
<Tab id="tab2" eventKey={1} title={<TabTitleText>"Tab item 2"</TabTitleText>}>
Tab 2 section
</Tab>
</Tabs>
);

expect(asFragment()).toMatchSnapshot();
});

test('should render tablist aria-labelledby when provided', () => {
const { asFragment } = render(
<>
<h2 id="tablistHeading">My tabs heading</h2>
<Tabs id="tabListLabelledByTabs" tabListAriaLabelledBy="tablistHeading">
<Tab id="tab1" eventKey={0} title={<TabTitleText>"Tab item 1"</TabTitleText>}>
Tab 1 section
</Tab>
<Tab id="tab2" eventKey={1} title={<TabTitleText>"Tab item 2"</TabTitleText>}>
Tab 2 section
</Tab>
</Tabs>
</>
);

expect(asFragment()).toMatchSnapshot();
});

test('should not render tablist aria-label or aria-labelledby when neither is provided', () => {
const { asFragment } = render(
<Tabs id="noTabListLabelTabs">
<Tab id="tab1" eventKey={0} title={<TabTitleText>"Tab item 1"</TabTitleText>}>
Tab 1 section
</Tab>
<Tab id="tab2" eventKey={1} title={<TabTitleText>"Tab item 2"</TabTitleText>}>
Tab 2 section
</Tab>
</Tabs>
);

expect(asFragment()).toMatchSnapshot();
});
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,266 @@ exports[`should render subtabs 1`] = `
</DocumentFragment>
`;

exports[`should render tablist aria-label when provided 1`] = `
<DocumentFragment>
<div
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
data-ouia-component-id="OUIA-Generated-Tabs-38"
data-ouia-component-type="PF6/Tabs"
data-ouia-safe="true"
id="tabListLabelTabs"
style="--pf-v6-c-tabs--link-accent--length: 0px; --pf-v6-c-tabs--link-accent--start: 0px;"
>
<ul
aria-label="Primary tab list"
class="pf-v6-c-tabs__list"
role="tablist"
>
<li
class="pf-v6-c-tabs__item pf-m-current"
role="presentation"
>
<button
aria-controls="pf-tab-section-0-tab1"
aria-selected="true"
class="pf-v6-c-tabs__link"
data-ouia-component-type="PF6/TabButton"
data-ouia-safe="true"
id="pf-tab-0-tab1"
role="tab"
type="button"
>
<span
class="pf-v6-c-tabs__item-text"
>
"Tab item 1"
</span>
</button>
</li>
<li
class="pf-v6-c-tabs__item"
role="presentation"
>
<button
aria-controls="pf-tab-section-1-tab2"
aria-selected="false"
class="pf-v6-c-tabs__link"
data-ouia-component-type="PF6/TabButton"
data-ouia-safe="true"
id="pf-tab-1-tab2"
role="tab"
type="button"
>
<span
class="pf-v6-c-tabs__item-text"
>
"Tab item 2"
</span>
</button>
</li>
</ul>
</div>
<section
aria-labelledby="pf-tab-0-tab1"
class="pf-v6-c-tab-content"
data-ouia-component-type="PF6/TabContent"
data-ouia-safe="true"
id="pf-tab-section-0-tab1"
role="tabpanel"
tabindex="0"
>
Tab 1 section
</section>
<section
aria-labelledby="pf-tab-1-tab2"
class="pf-v6-c-tab-content"
data-ouia-component-type="PF6/TabContent"
data-ouia-safe="true"
hidden=""
id="pf-tab-section-1-tab2"
role="tabpanel"
tabindex="0"
>
Tab 2 section
</section>
</DocumentFragment>
`;

exports[`should render tablist aria-labelledby when provided 1`] = `
<DocumentFragment>
<h2
id="tablistHeading"
>
My tabs heading
</h2>
<div
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
data-ouia-component-id="OUIA-Generated-Tabs-39"
data-ouia-component-type="PF6/Tabs"
data-ouia-safe="true"
id="tabListLabelledByTabs"
style="--pf-v6-c-tabs--link-accent--length: 0px; --pf-v6-c-tabs--link-accent--start: 0px;"
>
<ul
aria-labelledby="tablistHeading"
class="pf-v6-c-tabs__list"
role="tablist"
>
<li
class="pf-v6-c-tabs__item pf-m-current"
role="presentation"
>
<button
aria-controls="pf-tab-section-0-tab1"
aria-selected="true"
class="pf-v6-c-tabs__link"
data-ouia-component-type="PF6/TabButton"
data-ouia-safe="true"
id="pf-tab-0-tab1"
role="tab"
type="button"
>
<span
class="pf-v6-c-tabs__item-text"
>
"Tab item 1"
</span>
</button>
</li>
<li
class="pf-v6-c-tabs__item"
role="presentation"
>
<button
aria-controls="pf-tab-section-1-tab2"
aria-selected="false"
class="pf-v6-c-tabs__link"
data-ouia-component-type="PF6/TabButton"
data-ouia-safe="true"
id="pf-tab-1-tab2"
role="tab"
type="button"
>
<span
class="pf-v6-c-tabs__item-text"
>
"Tab item 2"
</span>
</button>
</li>
</ul>
</div>
<section
aria-labelledby="pf-tab-0-tab1"
class="pf-v6-c-tab-content"
data-ouia-component-type="PF6/TabContent"
data-ouia-safe="true"
id="pf-tab-section-0-tab1"
role="tabpanel"
tabindex="0"
>
Tab 1 section
</section>
<section
aria-labelledby="pf-tab-1-tab2"
class="pf-v6-c-tab-content"
data-ouia-component-type="PF6/TabContent"
data-ouia-safe="true"
hidden=""
id="pf-tab-section-1-tab2"
role="tabpanel"
tabindex="0"
>
Tab 2 section
</section>
</DocumentFragment>
`;


exports[`should not render tablist aria-label or aria-labelledby when neither is provided 1`] = `
<DocumentFragment>
<div
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
data-ouia-component-id="OUIA-Generated-Tabs-40"
data-ouia-component-type="PF6/Tabs"
data-ouia-safe="true"
id="noTabListLabelTabs"
style="--pf-v6-c-tabs--link-accent--length: 0px; --pf-v6-c-tabs--link-accent--start: 0px;"
>
<ul
class="pf-v6-c-tabs__list"
role="tablist"
>
<li
class="pf-v6-c-tabs__item pf-m-current"
role="presentation"
>
<button
aria-controls="pf-tab-section-0-tab1"
aria-selected="true"
class="pf-v6-c-tabs__link"
data-ouia-component-type="PF6/TabButton"
data-ouia-safe="true"
id="pf-tab-0-tab1"
role="tab"
type="button"
>
<span
class="pf-v6-c-tabs__item-text"
>
"Tab item 1"
</span>
</button>
</li>
<li
class="pf-v6-c-tabs__item"
role="presentation"
>
<button
aria-controls="pf-tab-section-1-tab2"
aria-selected="false"
class="pf-v6-c-tabs__link"
data-ouia-component-type="PF6/TabButton"
data-ouia-safe="true"
id="pf-tab-1-tab2"
role="tab"
type="button"
>
<span
class="pf-v6-c-tabs__item-text"
>
"Tab item 2"
</span>
</button>
</li>
</ul>
</div>
<section
aria-labelledby="pf-tab-0-tab1"
class="pf-v6-c-tab-content"
data-ouia-component-type="PF6/TabContent"
data-ouia-safe="true"
id="pf-tab-section-0-tab1"
role="tabpanel"
tabindex="0"
>
Tab 1 section
</section>
<section
aria-labelledby="pf-tab-1-tab2"
class="pf-v6-c-tab-content"
data-ouia-component-type="PF6/TabContent"
data-ouia-safe="true"
hidden=""
id="pf-tab-section-1-tab2"
role="tabpanel"
tabindex="0"
>
Tab 2 section
</section>
</DocumentFragment>
`;

exports[`should render tabs with eventKey Strings 1`] = `
<DocumentFragment>
<div
Expand Down
2 changes: 2 additions & 0 deletions packages/react-core/src/components/Tabs/examples/Tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ Use subtabs within other components, like modals. Subtabs have less visually pro

To apply subtab styling to tabs, use the `isSubtab` property.

For accessibility, give the primary tablist an accessible name (for example, `tabListAriaLabel="Primary"`) and give any subtab tablist an accessible name that matches the currently selected primary tab (for example, `tabListAriaLabel="Users"`).

```ts file="./TabsSubtabs.tsx"

```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const TabsNavSubtab: React.FunctionComponent = () => {
onSelect={handleTabClickFirst}
component={TabsComponent.nav}
aria-label="Tabs in the sub tabs with nav element example"
tabListAriaLabel="Primary"
>
<Tab eventKey={0} title={<TabTitleText>Users</TabTitleText>} href="#" aria-label="Subtabs with nav content users">
<Tabs
Expand All @@ -35,6 +36,7 @@ export const TabsNavSubtab: React.FunctionComponent = () => {
onSelect={handleTabClickSecond}
aria-label="Local secondary"
component={TabsComponent.nav}
tabListAriaLabel="Users"
>
<Tab eventKey={20} title={<TabTitleText>Item 1</TabTitleText>} href="#">
Item 1 item section
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const TabsSubtabs: React.FunctionComponent = () => {
onSelect={handleTabClickFirst}
isBox={isBox}
aria-label="Tabs in the tabs with subtabs example"
tabListAriaLabel="Primary"
role="region"
>
<Tab eventKey={0} title={<TabTitleText>Users</TabTitleText>} aria-label="Tabs with subtabs content users">
Expand All @@ -41,6 +42,7 @@ export const TabsSubtabs: React.FunctionComponent = () => {
role="region"
activeKey={activeTabKey2}
isSubtab
tabListAriaLabel="Users"
onSelect={handleTabClickSecond}
>
<Tab eventKey={20} title={<TabTitleText>Subtab item 1</TabTitleText>}>
Expand Down
Loading