Skip to content

Commit 5d9a05b

Browse files
authored
fix(tabs): vertical tabs support for accessible navigation and aria-orientation (#5924)
1 parent bc4c982 commit 5d9a05b

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

.changeset/ninety-hairs-heal.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@heroui/tabs": patch
3+
---
4+
5+
Fix vertical tabs to use correct aria-orientation and support Up/Down arrow navigation for accessibility. (#5810)

packages/components/tabs/__tests__/tabs.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,4 +539,63 @@ describe("Tabs", () => {
539539
expect(newTabButtons[2]).toHaveAttribute("aria-selected", "true");
540540
});
541541
});
542+
543+
test("should have correct aria-orientation for vertical tabs", () => {
544+
const wrapper = render(
545+
<Tabs isVertical aria-label="Vertical tabs test">
546+
<Tab key="item1" title="Item 1">
547+
<div>Content 1</div>
548+
</Tab>
549+
<Tab key="item2" title="Item 2">
550+
<div>Content 2</div>
551+
</Tab>
552+
<Tab key="item3" title="Item 3">
553+
<div>Content 3</div>
554+
</Tab>
555+
</Tabs>,
556+
);
557+
558+
const tablist = wrapper.getByRole("tablist");
559+
560+
expect(tablist).toHaveAttribute("aria-orientation", "vertical");
561+
});
562+
563+
test("should navigate vertical tabs with ArrowUp and ArrowDown keys", async () => {
564+
const wrapper = render(
565+
<Tabs isVertical aria-label="Vertical tabs keyboard test">
566+
<Tab key="item1" title="Item 1">
567+
<div>Content 1</div>
568+
</Tab>
569+
<Tab key="item2" title="Item 2">
570+
<div>Content 2</div>
571+
</Tab>
572+
<Tab key="item3" title="Item 3">
573+
<div>Content 3</div>
574+
</Tab>
575+
</Tabs>,
576+
);
577+
578+
const tab1 = wrapper.getByRole("tab", {name: "Item 1"});
579+
const tab2 = wrapper.getByRole("tab", {name: "Item 2"});
580+
const tab3 = wrapper.getByRole("tab", {name: "Item 3"});
581+
582+
act(() => {
583+
focus(tab1);
584+
});
585+
586+
await user.keyboard("[ArrowDown]");
587+
expect(tab1).toHaveAttribute("aria-selected", "false");
588+
expect(tab2).toHaveAttribute("aria-selected", "true");
589+
expect(tab3).toHaveAttribute("aria-selected", "false");
590+
591+
await user.keyboard("[ArrowDown]");
592+
expect(tab1).toHaveAttribute("aria-selected", "false");
593+
expect(tab2).toHaveAttribute("aria-selected", "false");
594+
expect(tab3).toHaveAttribute("aria-selected", "true");
595+
596+
await user.keyboard("[ArrowUp]");
597+
expect(tab1).toHaveAttribute("aria-selected", "false");
598+
expect(tab2).toHaveAttribute("aria-selected", "true");
599+
expect(tab3).toHaveAttribute("aria-selected", "false");
600+
});
542601
});

packages/components/tabs/src/use-tabs.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,19 @@ export function useTabs<T extends object>(originalProps: UseTabsProps<T>) {
106106
const disableAnimation =
107107
originalProps?.disableAnimation ?? globalContext?.disableAnimation ?? false;
108108

109+
const placement = (variantProps as Props).placement ?? (isVertical ? "start" : "top");
110+
const orientation =
111+
isVertical || placement === "start" || placement === "end" ? "vertical" : "horizontal";
112+
109113
const state = useTabListState<T>({
110114
children: children as CollectionChildren<T>,
111115
...otherProps,
112116
});
113-
const {tabListProps} = useTabList<T>(otherProps as AriaTabListProps<T>, state, domRef);
117+
const {tabListProps} = useTabList<T>(
118+
{...otherProps, orientation} as AriaTabListProps<T>,
119+
state,
120+
domRef,
121+
);
114122

115123
const slots = useMemo(
116124
() =>
@@ -161,7 +169,6 @@ export function useTabs<T extends object>(originalProps: UseTabsProps<T>) {
161169
[baseStyles, otherProps, slots],
162170
);
163171

164-
const placement = (variantProps as Props).placement ?? (isVertical ? "start" : "top");
165172
const getWrapperProps: PropGetter = useCallback(
166173
(props) => ({
167174
"data-slot": "tabWrapper",

0 commit comments

Comments
 (0)