Skip to content

Commit 21897e7

Browse files
authored
Merge pull request #152 from BrianRid/feat/keyboard-nav-in-tabs
feat: adding keyboard navigation to Tabs component for accessibility …
2 parents dfadd75 + 2f65c4e commit 21897e7

File tree

1 file changed

+37
-2
lines changed

1 file changed

+37
-2
lines changed

src/Tabs.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,14 @@ export const Tabs = memo(
8383
return index === -1 ? 0 : index;
8484
};
8585

86+
const buttonRefs = React.useRef<Array<HTMLButtonElement | null>>([]);
87+
8688
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(getSelectedTabIndex);
8789

8890
useEffect(() => {
8991
if (selectedTabId === undefined) {
9092
return;
9193
}
92-
9394
setSelectedTabIndex(getSelectedTabIndex());
9495
}, [selectedTabId]);
9596

@@ -104,6 +105,34 @@ export const Tabs = memo(
104105
}
105106
});
106107

108+
const onKeyboardNavigation = (
109+
event: React.KeyboardEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLUListElement>
110+
) => {
111+
let targetIndex = selectedTabIndex;
112+
switch (event.key) {
113+
case "ArrowRight":
114+
targetIndex = selectedTabIndex < tabs.length - 1 ? selectedTabIndex + 1 : 0;
115+
break;
116+
case "ArrowLeft":
117+
targetIndex = selectedTabIndex === 0 ? tabs.length - 1 : selectedTabIndex - 1;
118+
break;
119+
case "Home":
120+
targetIndex = 0;
121+
break;
122+
case "End":
123+
targetIndex = tabs.length - 1;
124+
break;
125+
}
126+
buttonRefs.current[targetIndex]?.click();
127+
};
128+
129+
React.useEffect(() => {
130+
const targetTabButton = buttonRefs.current[selectedTabIndex];
131+
if (targetTabButton) {
132+
targetTabButton.focus();
133+
}
134+
}, [selectedTabIndex]);
135+
107136
const { getPanelId, getTabId } = (function useClosure() {
108137
const id = useId();
109138

@@ -124,10 +153,16 @@ export const Tabs = memo(
124153
style={style}
125154
{...rest}
126155
>
127-
<ul className={fr.cx("fr-tabs__list")} role="tablist" aria-label={label}>
156+
<ul
157+
className={fr.cx("fr-tabs__list")}
158+
role="tablist"
159+
aria-label={label}
160+
onKeyDownCapture={e => onKeyboardNavigation(e)}
161+
>
128162
{tabs.map(({ label, iconId }, tabIndex) => (
129163
<li key={label + (iconId ?? "")} role="presentation">
130164
<button
165+
ref={button => (buttonRefs.current[tabIndex] = button)}
131166
id={getTabId(tabIndex)}
132167
className={cx(
133168
fr.cx("fr-tabs__tab", iconId, "fr-tabs__tab--icon-left"),

0 commit comments

Comments
 (0)