Skip to content

Commit a7d85d7

Browse files
feat(Tabs): add tabListAriaLabel prop (#12193)
* feat(Tabs): add tabListAriaLabel prop Signed-off-by: Mohamed Fall <ps.hackmaster@gmail.com> * 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 <ps.hackmaster@gmail.com> --------- Signed-off-by: Mohamed Fall <ps.hackmaster@gmail.com> 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

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<TabsProps, TabsState> {
499503
toggleText,
500504
toggleAriaLabel,
501505
addButtonAriaLabel,
506+
tabListAriaLabel,
507+
tabListAriaLabelledBy,
502508
onToggle,
503509
onClose,
504510
onAdd,
@@ -625,7 +631,14 @@ class Tabs extends Component<TabsProps, TabsState> {
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 heading</h2>
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>Users</TabTitleText>} 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 1</TabTitleText>} 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
role="region"
3738
>
3839
<Tab eventKey={0} title={<TabTitleText>Users</TabTitleText>} aria-label="Tabs with subtabs content users">
@@ -41,6 +42,7 @@ export const TabsSubtabs: React.FunctionComponent = () => {
4142
role="region"
4243
activeKey={activeTabKey2}
4344
isSubtab
45+
tabListAriaLabel="Users"
4446
onSelect={handleTabClickSecond}
4547
>
4648
<Tab eventKey={20} title={<TabTitleText>Subtab item 1</TabTitleText>}>

0 commit comments

Comments
 (0)