Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a7d85d7

Browse files
feat(Tabs): add tabListAriaLabel prop (#12193)
* feat(Tabs): add tabListAriaLabel prop Signed-off-by: Mohamed Fall * Update packages/react-core/src/components/Tabs/Tabs.tsx Co-authored-by: Eric Olkowski <70952936+thatblindgeye@users.noreply.github.com> * feat(Tabs): add tabListAriaLabelledBy prop, update accessibility tests, and examples Signed-off-by: Mohamed Fall --------- Signed-off-by: Mohamed Fall Co-authored-by: Eric Olkowski <70952936+thatblindgeye@users.noreply.github.com>
1 parent 04dc092 commit a7d85d7

File tree

6 files changed

+328
-1
lines changed
  • packages/react-core/src/components/Tabs
    • Tabs.tsx
    • __tests__
      • Tabs.test.tsx
      • __snapshots__
        • Tabs.test.tsx.snap
    • examples
      • Tabs.md
      • TabsNavSubtab.tsx
      • TabsSubtabs.tsx

6 files changed

+328
-1
lines changed

packages/react-core/src/components/Tabs/Tabs.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export interface TabsProps
6060
onAdd?: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
6161
/** Aria-label for the add button */
6262
addButtonAriaLabel?: string;
63+
/** 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. */
64+
tabListAriaLabel?: string;
65+
/** Id of an element that provides an accessible name for the tablist. Use this when a visible label already exists on the page. */
66+
tabListAriaLabelledBy?: string;
6367
/** Uniquely identifies the tabs */
6468
id?: string;
6569
/** Flag indicating that the add button is disabled when onAdd is passed in */
@@ -499,6 +503,8 @@ class Tabs extends Component {
499503
toggleText,
500504
toggleAriaLabel,
501505
addButtonAriaLabel,
506+
tabListAriaLabel,
507+
tabListAriaLabelledBy,
502508
onToggle,
503509
onClose,
504510
onAdd,
@@ -625,7 +631,14 @@ class Tabs extends Component {
625631
/>
626632
div>
627633
)}
628-
<ul className={css(styles.tabsList)} ref={this.tabList} onScroll={this.handleScrollButtons} role="tablist">
634+
<ul
635+
aria-label={tabListAriaLabel}
636+
aria-labelledby={tabListAriaLabelledBy}
637+
className={css(styles.tabsList)}
638+
ref={this.tabList}
639+
onScroll={this.handleScrollButtons}
640+
role="tablist"
641+
>
629642
{isOverflowHorizontal ? filteredChildrenWithoutOverflow : filteredChildren}
630643
{hasOverflowTab && <OverflowTab overflowingTabs={overflowingTabProps} {...overflowObjectProps} />}
631644
ul>

packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,51 @@ test(`should render with custom inline style and accent position inline style`,
742742

743743
expect(screen.getByRole('region')).toHaveStyle(`background-color: #12345;--pf-v6-c-tabs--link-accent--start: 0px;`);
744744
});
745+
746+
test('should render tablist aria-label when provided', () => {
747+
const { asFragment } = render(
748+
<Tabs id="tabListLabelTabs" tabListAriaLabel="Primary tab list">
749+
<Tab id="tab1" eventKey={0} title={<TabTitleText>"Tab item 1"TabTitleText>}>
750+
Tab 1 section
751+
Tab>
752+
<Tab id="tab2" eventKey={1} title={<TabTitleText>"Tab item 2"TabTitleText>}>
753+
Tab 2 section
754+
Tab>
755+
Tabs>
756+
);
757+
758+
expect(asFragment()).toMatchSnapshot();
759+
});
760+
761+
test('should render tablist aria-labelledby when provided', () => {
762+
const { asFragment } = render(
763+
<>
764+
<h2 id="tablistHeading">My tabs headingh2>
765+
<Tabs id="tabListLabelledByTabs" tabListAriaLabelledBy="tablistHeading">
766+
<Tab id="tab1" eventKey={0} title={<TabTitleText>"Tab item 1"TabTitleText>}>
767+
Tab 1 section
768+
Tab>
769+
<Tab id="tab2" eventKey={1} title={<TabTitleText>"Tab item 2"TabTitleText>}>
770+
Tab 2 section
771+
Tab>
772+
Tabs>
773+
>
774+
);
775+
776+
expect(asFragment()).toMatchSnapshot();
777+
});
778+
779+
test('should not render tablist aria-label or aria-labelledby when neither is provided', () => {
780+
const { asFragment } = render(
781+
<Tabs id="noTabListLabelTabs">
782+
<Tab id="tab1" eventKey={0} title={<TabTitleText>"Tab item 1"TabTitleText>}>
783+
Tab 1 section
784+
Tab>
785+
<Tab id="tab2" eventKey={1} title={<TabTitleText>"Tab item 2"TabTitleText>}>
786+
Tab 2 section
787+
Tab>
788+
Tabs>
789+
);
790+
791+
expect(asFragment()).toMatchSnapshot();
792+
});

packages/react-core/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,266 @@ exports[`should render subtabs 1`] = `
11181118
DocumentFragment>
11191119
`;
11201120

1121+
exports[`should render tablist aria-label when provided 1`] = `
1122+
<DocumentFragment>
1123+
<div
1124+
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1125+
data-ouia-component-id="OUIA-Generated-Tabs-38"
1126+
data-ouia-component-type="PF6/Tabs"
1127+
data-ouia-safe="true"
1128+
id="tabListLabelTabs"
1129+
style="--pf-v6-c-tabs--link-accent--length: 0px; --pf-v6-c-tabs--link-accent--start: 0px;"
1130+
>
1131+
<ul
1132+
aria-label="Primary tab list"
1133+
class="pf-v6-c-tabs__list"
1134+
role="tablist"
1135+
>
1136+
<li
1137+
class="pf-v6-c-tabs__item pf-m-current"
1138+
role="presentation"
1139+
>
1140+
<button
1141+
aria-controls="pf-tab-section-0-tab1"
1142+
aria-selected="true"
1143+
class="pf-v6-c-tabs__link"
1144+
data-ouia-component-type="PF6/TabButton"
1145+
data-ouia-safe="true"
1146+
id="pf-tab-0-tab1"
1147+
role="tab"
1148+
type="button"
1149+
>
1150+
<span
1151+
class="pf-v6-c-tabs__item-text"
1152+
>
1153+
"Tab item 1"
1154+
span>
1155+
button>
1156+
li>
1157+
<li
1158+
class="pf-v6-c-tabs__item"
1159+
role="presentation"
1160+
>
1161+
<button
1162+
aria-controls="pf-tab-section-1-tab2"
1163+
aria-selected="false"
1164+
class="pf-v6-c-tabs__link"
1165+
data-ouia-component-type="PF6/TabButton"
1166+
data-ouia-safe="true"
1167+
id="pf-tab-1-tab2"
1168+
role="tab"
1169+
type="button"
1170+
>
1171+
<span
1172+
class="pf-v6-c-tabs__item-text"
1173+
>
1174+
"Tab item 2"
1175+
span>
1176+
button>
1177+
li>
1178+
ul>
1179+
div>
1180+
<section
1181+
aria-labelledby="pf-tab-0-tab1"
1182+
class="pf-v6-c-tab-content"
1183+
data-ouia-component-type="PF6/TabContent"
1184+
data-ouia-safe="true"
1185+
id="pf-tab-section-0-tab1"
1186+
role="tabpanel"
1187+
tabindex="0"
1188+
>
1189+
Tab 1 section
1190+
section>
1191+
<section
1192+
aria-labelledby="pf-tab-1-tab2"
1193+
class="pf-v6-c-tab-content"
1194+
data-ouia-component-type="PF6/TabContent"
1195+
data-ouia-safe="true"
1196+
hidden=""
1197+
id="pf-tab-section-1-tab2"
1198+
role="tabpanel"
1199+
tabindex="0"
1200+
>
1201+
Tab 2 section
1202+
section>
1203+
DocumentFragment>
1204+
`;
1205+
1206+
exports[`should render tablist aria-labelledby when provided 1`] = `
1207+
<DocumentFragment>
1208+
<h2
1209+
id="tablistHeading"
1210+
>
1211+
My tabs heading
1212+
h2>
1213+
<div
1214+
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1215+
data-ouia-component-id="OUIA-Generated-Tabs-39"
1216+
data-ouia-component-type="PF6/Tabs"
1217+
data-ouia-safe="true"
1218+
id="tabListLabelledByTabs"
1219+
style="--pf-v6-c-tabs--link-accent--length: 0px; --pf-v6-c-tabs--link-accent--start: 0px;"
1220+
>
1221+
<ul
1222+
aria-labelledby="tablistHeading"
1223+
class="pf-v6-c-tabs__list"
1224+
role="tablist"
1225+
>
1226+
<li
1227+
class="pf-v6-c-tabs__item pf-m-current"
1228+
role="presentation"
1229+
>
1230+
<button
1231+
aria-controls="pf-tab-section-0-tab1"
1232+
aria-selected="true"
1233+
class="pf-v6-c-tabs__link"
1234+
data-ouia-component-type="PF6/TabButton"
1235+
data-ouia-safe="true"
1236+
id="pf-tab-0-tab1"
1237+
role="tab"
1238+
type="button"
1239+
>
1240+
<span
1241+
class="pf-v6-c-tabs__item-text"
1242+
>
1243+
"Tab item 1"
1244+
span>
1245+
button>
1246+
li>
1247+
<li
1248+
class="pf-v6-c-tabs__item"
1249+
role="presentation"
1250+
>
1251+
<button
1252+
aria-controls="pf-tab-section-1-tab2"
1253+
aria-selected="false"
1254+
class="pf-v6-c-tabs__link"
1255+
data-ouia-component-type="PF6/TabButton"
1256+
data-ouia-safe="true"
1257+
id="pf-tab-1-tab2"
1258+
role="tab"
1259+
type="button"
1260+
>
1261+
<span
1262+
class="pf-v6-c-tabs__item-text"
1263+
>
1264+
"Tab item 2"
1265+
span>
1266+
button>
1267+
li>
1268+
ul>
1269+
div>
1270+
<section
1271+
aria-labelledby="pf-tab-0-tab1"
1272+
class="pf-v6-c-tab-content"
1273+
data-ouia-component-type="PF6/TabContent"
1274+
data-ouia-safe="true"
1275+
id="pf-tab-section-0-tab1"
1276+
role="tabpanel"
1277+
tabindex="0"
1278+
>
1279+
Tab 1 section
1280+
section>
1281+
<section
1282+
aria-labelledby="pf-tab-1-tab2"
1283+
class="pf-v6-c-tab-content"
1284+
data-ouia-component-type="PF6/TabContent"
1285+
data-ouia-safe="true"
1286+
hidden=""
1287+
id="pf-tab-section-1-tab2"
1288+
role="tabpanel"
1289+
tabindex="0"
1290+
>
1291+
Tab 2 section
1292+
section>
1293+
DocumentFragment>
1294+
`;
1295+
1296+
1297+
exports[`should not render tablist aria-label or aria-labelledby when neither is provided 1`] = `
1298+
<DocumentFragment>
1299+
<div
1300+
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1301+
data-ouia-component-id="OUIA-Generated-Tabs-40"
1302+
data-ouia-component-type="PF6/Tabs"
1303+
data-ouia-safe="true"
1304+
id="noTabListLabelTabs"
1305+
style="--pf-v6-c-tabs--link-accent--length: 0px; --pf-v6-c-tabs--link-accent--start: 0px;"
1306+
>
1307+
<ul
1308+
class="pf-v6-c-tabs__list"
1309+
role="tablist"
1310+
>
1311+
<li
1312+
class="pf-v6-c-tabs__item pf-m-current"
1313+
role="presentation"
1314+
>
1315+
<button
1316+
aria-controls="pf-tab-section-0-tab1"
1317+
aria-selected="true"
1318+
class="pf-v6-c-tabs__link"
1319+
data-ouia-component-type="PF6/TabButton"
1320+
data-ouia-safe="true"
1321+
id="pf-tab-0-tab1"
1322+
role="tab"
1323+
type="button"
1324+
>
1325+
<span
1326+
class="pf-v6-c-tabs__item-text"
1327+
>
1328+
"Tab item 1"
1329+
span>
1330+
button>
1331+
li>
1332+
<li
1333+
class="pf-v6-c-tabs__item"
1334+
role="presentation"
1335+
>
1336+
<button
1337+
aria-controls="pf-tab-section-1-tab2"
1338+
aria-selected="false"
1339+
class="pf-v6-c-tabs__link"
1340+
data-ouia-component-type="PF6/TabButton"
1341+
data-ouia-safe="true"
1342+
id="pf-tab-1-tab2"
1343+
role="tab"
1344+
type="button"
1345+
>
1346+
<span
1347+
class="pf-v6-c-tabs__item-text"
1348+
>
1349+
"Tab item 2"
1350+
span>
1351+
button>
1352+
li>
1353+
ul>
1354+
div>
1355+
<section
1356+
aria-labelledby="pf-tab-0-tab1"
1357+
class="pf-v6-c-tab-content"
1358+
data-ouia-component-type="PF6/TabContent"
1359+
data-ouia-safe="true"
1360+
id="pf-tab-section-0-tab1"
1361+
role="tabpanel"
1362+
tabindex="0"
1363+
>
1364+
Tab 1 section
1365+
section>
1366+
<section
1367+
aria-labelledby="pf-tab-1-tab2"
1368+
class="pf-v6-c-tab-content"
1369+
data-ouia-component-type="PF6/TabContent"
1370+
data-ouia-safe="true"
1371+
hidden=""
1372+
id="pf-tab-section-1-tab2"
1373+
role="tabpanel"
1374+
tabindex="0"
1375+
>
1376+
Tab 2 section
1377+
section>
1378+
DocumentFragment>
1379+
`;
1380+
11211381
exports[`should render tabs with eventKey Strings 1`] = `
11221382
<DocumentFragment>
11231383
<div

packages/react-core/src/components/Tabs/examples/Tabs.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ Use subtabs within other components, like modals. Subtabs have less visually pro
159159

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

162+
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"`).
163+
162164
```ts file="./TabsSubtabs.tsx"
163165

164166
```

packages/react-core/src/components/Tabs/examples/TabsNavSubtab.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const TabsNavSubtab: React.FunctionComponent = () => {
2727
onSelect={handleTabClickFirst}
2828
component={TabsComponent.nav}
2929
aria-label="Tabs in the sub tabs with nav element example"
30+
tabListAriaLabel="Primary"
3031
>
3132
<Tab eventKey={0} title={<TabTitleText>UsersTabTitleText>} href="#" aria-label="Subtabs with nav content users">
3233
<Tabs
@@ -35,6 +36,7 @@ export const TabsNavSubtab: React.FunctionComponent = () => {
3536
onSelect={handleTabClickSecond}
3637
aria-label="Local secondary"
3738
component={TabsComponent.nav}
39+
tabListAriaLabel="Users"
3840
>
3941
<Tab eventKey={20} title={<TabTitleText>Item 1TabTitleText>} href="#">
4042
Item 1 item section

packages/react-core/src/components/Tabs/examples/TabsSubtabs.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const TabsSubtabs: React.FunctionComponent = () => {
3333
onSelect={handleTabClickFirst}
3434
isBox={isBox}
3535
aria-label="Tabs in the tabs with subtabs example"
36+
tabListAriaLabel="Primary"
3637