Skip to content

Conversation

@anusha-c18
Copy link
Contributor

@anusha-c18 anusha-c18 commented Oct 14, 2025

Closes #5810

📝 Description

This PR fixes the accessibility behavior for vertical tabs in the tabs component.
Previously, vertical tabs were not correctly using aria-orientation="vertical" and did not respond to ArrowUp/ArrowDown keys for keyboard navigation.

Screenshot 2025-10-15 at 3 28 17 AM

This update ensures that the correct ARIA orientation is applied with up/down keyboard navigation, and adds tests for the fixes.

⛳️ Current behavior (updates)

  • All tab lists used aria-orientation="horizontal" by default, even when the layout was vertical.
  • Vertical tabs only responded to Left and Right arrow keys, which is incorrect per WAI-ARIA guidelines.

🚀 New behavior

  • Vertical tabs now set aria-orientation="vertical".
  • Up and down keys correctly move focus and selection between vertical tabs.
  • Added dedicated tests in tabs.test.tsx to verify both correct aria-orientation assignment and keyboard navigation behavior for vertical tabs.

💣 Is this a breaking change (Yes/No):

No.

📝 Additional Information

The React Aria library intentionally allows all arrow keys (up, down, left, right) for vertical tabs to support users moving in both axes, so this behavior has been preserved.

Summary by CodeRabbit

  • Bug Fixes

    • Vertical Tabs now use the correct aria-orientation, ensuring accurate focus and selection behavior.
    • Up/Down arrow keyboard navigation works correctly in vertical layouts.
  • Accessibility

    • Improved screen reader support for vertical Tabs via correct ARIA attributes and predictable keyboard interactions.
  • Tests

    • Added test coverage for vertical orientation and Up/Down arrow navigation.

@changeset-bot
Copy link

changeset-bot bot commented Oct 14, 2025

🦋 Changeset detected

Latest commit: 15a3c05

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@heroui/tabs Patch
@heroui/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Oct 14, 2025

@anusha-c18 is attempting to deploy a commit to the HeroUI Inc Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

Fixes vertical Tabs accessibility by deriving orientation from props, setting aria-orientation="vertical" when appropriate, passing orientation into the tab-list state, adding data attributes, adjusting prop merging, and adding tests and a changeset validating Up/Down arrow navigation for vertical tabs.

Changes

Cohort / File(s) Summary of changes
Tabs hook changes
packages/components/tabs/src/use-tabs.ts
Derives placement and orientation from variant props and isVertical; passes orientation into useTabList; sets aria-orientation on the tab list; adds data-placement and data-vertical on wrapper; uses mergeProps for tab list props; removes duplicated placement declaration.
Tests for vertical behavior
packages/components/tabs/__tests__/tabs.test.tsx
Adds tests asserting aria-orientation="vertical" when isVertical is used and verifying ArrowDown/ArrowUp keyboard navigation updates focus and aria-selected for vertical tabs.
Release notes / changeset
.changeset/five-kiwis-carry.md
Adds a patch changeset documenting the fix for vertical tabs to use correct aria-orientation and support Up/Down arrow navigation.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Tabs as Tabs Component
  participant Hook as useTabs
  participant Aria as useTabList
  participant DOM as TabList DOM

  User->>Tabs: Render <Tabs isVertical|placement="start|end">
  Tabs->>Hook: initialize(props)
  Hook->>Hook: derive placement & orientation
  Hook->>Aria: init({ orientation })
  Aria-->>Hook: tabListProps (aria-orientation)
  Hook->>DOM: render TabList with aria-orientation + data-*
  Note right of DOM #lightblue: aria-orientation determines keyboard handlers

  User->>DOM: Press ArrowUp / ArrowDown
  DOM->>Aria: keyboard event -> request move
  Aria-->>DOM: update focus & aria-selected to prev/next tab
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • jrgarciadev
  • wingkwong

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately and concisely describes the primary change of adding correct ARIA orientation and keyboard navigation support for vertical tabs, directly reflecting the core fix implemented in the pull request.
Linked Issues Check ✅ Passed The changes fulfill the linked issue #5810 by correctly applying aria-orientation="vertical" for vertical tabs and implementing ArrowUp/ArrowDown key navigation with corresponding tests, satisfying the stated accessibility requirements.
Out of Scope Changes Check ✅ Passed All modifications—including the release note, updated useTabs logic, and new tests—are directly related to fixing vertical tab orientation and navigation and do not introduce any unrelated functionality.
Description Check ✅ Passed The description follows the required template by including the issue closure, a clear 📝 Description, sections for the current and new behavior, a breaking change declaration, and additional information, all of which are fully detailed and relevant.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@anusha-c18 anusha-c18 marked this pull request as ready for review October 14, 2025 22:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3737870 and 4ad633a.

📒 Files selected for processing (3)
  • .changeset/five-kiwis-carry.md (1 hunks)
  • packages/components/tabs/__tests__/tabs.test.tsx (1 hunks)
  • packages/components/tabs/src/use-tabs.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/components/tabs/__tests__/tabs.test.tsx (1)
packages/utilities/test-utils/src/focus.ts (1)
  • focus (6-12)
🪛 LanguageTool
.changeset/five-kiwis-carry.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...Down arrow navigation for accessibility.

(QB_NEW_EN)

🔇 Additional comments (5)
.changeset/five-kiwis-carry.md (1)

1-5: LGTM!

The changeset accurately describes the accessibility fix for vertical tabs.

packages/components/tabs/__tests__/tabs.test.tsx (2)

543-561: LGTM!

The test correctly verifies that vertical tabs have aria-orientation="vertical". The test structure follows existing patterns and appropriately uses the isVertical prop.


563-604: LGTM!

The test comprehensively verifies Up/Down arrow keyboard navigation for vertical tabs, checking that focus and aria-selected attributes update correctly. The sequential navigation pattern (Down → Down → Up) provides good coverage.

packages/components/tabs/src/use-tabs.ts (2)

116-120: LGTM!

Passing orientation to useTabList correctly enables React Aria's keyboard navigation handling, which responds to Up/Down arrows for vertical orientation and Left/Right for horizontal.


182-195: LGTM with a minor note.

The mergeProps approach correctly combines tabListProps from React Aria with custom attributes. The explicit aria-orientation on line 189 is technically redundant since React Aria's tabListProps should already include it (given orientation is passed to useTabList on line 117), but the redundancy is harmless and makes the attribute assignment explicit for clarity.

@anusha-c18 anusha-c18 force-pushed the fix/tabs-vertical-orientation branch from 4ad633a to 15a3c05 Compare October 14, 2025 23:06
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/components/tabs/__tests__/tabs.test.tsx (2)

563-604: Consider adding wrapping behavior tests for vertical navigation.

The test correctly verifies Up/Down arrow navigation for vertical tabs. However, the existing horizontal navigation test (lines 153-194) includes wrapping behavior (ArrowRight on the last tab wraps to the first). Consider adding similar assertions here to verify ArrowDown on tab3 wraps to tab1 and ArrowUp on tab1 wraps to tab3.

Additionally, the PR description mentions "React Aria intentionally allows all arrow keys for vertical tabs to support movement in both axes." Consider adding assertions to verify that vertical tabs still respond to Left/Right arrows to document this behavior.

Example diff for wrapping:

   await user.keyboard("[ArrowUp]");
   expect(tab1).toHaveAttribute("aria-selected", "false");
   expect(tab2).toHaveAttribute("aria-selected", "true");
   expect(tab3).toHaveAttribute("aria-selected", "false");
+
+  await user.keyboard("[ArrowUp]");
+  expect(tab1).toHaveAttribute("aria-selected", "true");
+  expect(tab2).toHaveAttribute("aria-selected", "false");
+  expect(tab3).toHaveAttribute("aria-selected", "false");
+
+  await user.keyboard("[ArrowUp]");
+  expect(tab1).toHaveAttribute("aria-selected", "false");
+  expect(tab2).toHaveAttribute("aria-selected", "false");
+  expect(tab3).toHaveAttribute("aria-selected", "true");

563-604: Consider adding tests for placement="start" and placement="end".

Per the PR objectives, tabs with placement="start" or placement="end" should also use aria-orientation="vertical" and support Up/Down navigation. The existing test at lines 278-303 verifies data-placement and data-vertical attributes for these placements, but doesn't verify aria-orientation or keyboard navigation.

Consider adding dedicated tests to verify that:

  1. placement="start" sets aria-orientation="vertical" on the tablist
  2. placement="end" sets aria-orientation="vertical" on the tablist
  3. Both placements support Up/Down arrow navigation
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ad633a and 15a3c05.

📒 Files selected for processing (3)
  • .changeset/five-kiwis-carry.md (1 hunks)
  • packages/components/tabs/__tests__/tabs.test.tsx (1 hunks)
  • packages/components/tabs/src/use-tabs.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/components/tabs/src/use-tabs.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/components/tabs/__tests__/tabs.test.tsx (1)
packages/utilities/test-utils/src/focus.ts (1)
  • focus (6-12)
🪛 LanguageTool
.changeset/five-kiwis-carry.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...Down arrow navigation for accessibility.

(QB_NEW_EN)

🔇 Additional comments (2)
.changeset/five-kiwis-carry.md (1)

1-5: LGTM!

The changeset format is correct and the description clearly documents the fix. The LanguageTool hint is a false positive.

packages/components/tabs/__tests__/tabs.test.tsx (1)

543-561: LGTM!

The test correctly verifies that isVertical sets aria-orientation="vertical" on the tablist, which aligns with the PR objectives.

@vercel
Copy link

vercel bot commented Oct 15, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
heroui Ready Ready Preview Comment Oct 15, 2025 4:28am
heroui-sb Ready Ready Preview Comment Oct 15, 2025 4:28am

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 15, 2025

Open in StackBlitz

@heroui/accordion

npm i https://pkg.pr.new/@heroui/accordion@5811

@heroui/alert

npm i https://pkg.pr.new/@heroui/alert@5811

@heroui/autocomplete

npm i https://pkg.pr.new/@heroui/autocomplete@5811

@heroui/avatar

npm i https://pkg.pr.new/@heroui/avatar@5811

@heroui/badge

npm i https://pkg.pr.new/@heroui/badge@5811

@heroui/breadcrumbs

npm i https://pkg.pr.new/@heroui/breadcrumbs@5811

@heroui/button

npm i https://pkg.pr.new/@heroui/button@5811

@heroui/calendar

npm i https://pkg.pr.new/@heroui/calendar@5811

@heroui/card

npm i https://pkg.pr.new/@heroui/card@5811

@heroui/checkbox

npm i https://pkg.pr.new/@heroui/checkbox@5811

@heroui/chip

npm i https://pkg.pr.new/@heroui/chip@5811

@heroui/code

npm i https://pkg.pr.new/@heroui/code@5811

@heroui/date-input

npm i https://pkg.pr.new/@heroui/date-input@5811

@heroui/date-picker

npm i https://pkg.pr.new/@heroui/date-picker@5811

@heroui/divider

npm i https://pkg.pr.new/@heroui/divider@5811

@heroui/drawer

npm i https://pkg.pr.new/@heroui/drawer@5811

@heroui/dropdown

npm i https://pkg.pr.new/@heroui/dropdown@5811

@heroui/form

npm i https://pkg.pr.new/@heroui/form@5811

@heroui/image

npm i https://pkg.pr.new/@heroui/image@5811

@heroui/input

npm i https://pkg.pr.new/@heroui/input@5811

@heroui/input-otp

npm i https://pkg.pr.new/@heroui/input-otp@5811

@heroui/kbd

npm i https://pkg.pr.new/@heroui/kbd@5811

@heroui/link

npm i https://pkg.pr.new/@heroui/link@5811

@heroui/listbox

npm i https://pkg.pr.new/@heroui/listbox@5811

@heroui/menu

npm i https://pkg.pr.new/@heroui/menu@5811

@heroui/modal

npm i https://pkg.pr.new/@heroui/modal@5811

@heroui/navbar

npm i https://pkg.pr.new/@heroui/navbar@5811

@heroui/number-input

npm i https://pkg.pr.new/@heroui/number-input@5811

@heroui/pagination

npm i https://pkg.pr.new/@heroui/pagination@5811

@heroui/popover

npm i https://pkg.pr.new/@heroui/popover@5811

@heroui/progress

npm i https://pkg.pr.new/@heroui/progress@5811

@heroui/radio

npm i https://pkg.pr.new/@heroui/radio@5811

@heroui/ripple

npm i https://pkg.pr.new/@heroui/ripple@5811

@heroui/scroll-shadow

npm i https://pkg.pr.new/@heroui/scroll-shadow@5811

@heroui/select

npm i https://pkg.pr.new/@heroui/select@5811

@heroui/skeleton

npm i https://pkg.pr.new/@heroui/skeleton@5811

@heroui/slider

npm i https://pkg.pr.new/@heroui/slider@5811

@heroui/snippet

npm i https://pkg.pr.new/@heroui/snippet@5811

@heroui/spacer

npm i https://pkg.pr.new/@heroui/spacer@5811

@heroui/spinner

npm i https://pkg.pr.new/@heroui/spinner@5811

@heroui/switch

npm i https://pkg.pr.new/@heroui/switch@5811

@heroui/table

npm i https://pkg.pr.new/@heroui/table@5811

@heroui/tabs

npm i https://pkg.pr.new/@heroui/tabs@5811

@heroui/toast

npm i https://pkg.pr.new/@heroui/toast@5811

@heroui/tooltip

npm i https://pkg.pr.new/@heroui/tooltip@5811

@heroui/user

npm i https://pkg.pr.new/@heroui/user@5811

@heroui/react

npm i https://pkg.pr.new/@heroui/react@5811

@heroui/system

npm i https://pkg.pr.new/@heroui/system@5811

@heroui/system-rsc

npm i https://pkg.pr.new/@heroui/system-rsc@5811

@heroui/theme

npm i https://pkg.pr.new/@heroui/theme@5811

@heroui/use-aria-accordion

npm i https://pkg.pr.new/@heroui/use-aria-accordion@5811

@heroui/use-aria-accordion-item

npm i https://pkg.pr.new/@heroui/use-aria-accordion-item@5811

@heroui/use-aria-button

npm i https://pkg.pr.new/@heroui/use-aria-button@5811

@heroui/use-aria-link

npm i https://pkg.pr.new/@heroui/use-aria-link@5811

@heroui/use-aria-modal-overlay

npm i https://pkg.pr.new/@heroui/use-aria-modal-overlay@5811

@heroui/use-aria-multiselect

npm i https://pkg.pr.new/@heroui/use-aria-multiselect@5811

@heroui/use-aria-overlay

npm i https://pkg.pr.new/@heroui/use-aria-overlay@5811

@heroui/use-callback-ref

npm i https://pkg.pr.new/@heroui/use-callback-ref@5811

@heroui/use-clipboard

npm i https://pkg.pr.new/@heroui/use-clipboard@5811

@heroui/use-data-scroll-overflow

npm i https://pkg.pr.new/@heroui/use-data-scroll-overflow@5811

@heroui/use-disclosure

npm i https://pkg.pr.new/@heroui/use-disclosure@5811

@heroui/use-draggable

npm i https://pkg.pr.new/@heroui/use-draggable@5811

@heroui/use-form-reset

npm i https://pkg.pr.new/@heroui/use-form-reset@5811

@heroui/use-image

npm i https://pkg.pr.new/@heroui/use-image@5811

@heroui/use-infinite-scroll

npm i https://pkg.pr.new/@heroui/use-infinite-scroll@5811

@heroui/use-intersection-observer

npm i https://pkg.pr.new/@heroui/use-intersection-observer@5811

@heroui/use-is-mobile

npm i https://pkg.pr.new/@heroui/use-is-mobile@5811

@heroui/use-is-mounted

npm i https://pkg.pr.new/@heroui/use-is-mounted@5811

@heroui/use-measure

npm i https://pkg.pr.new/@heroui/use-measure@5811

@heroui/use-pagination

npm i https://pkg.pr.new/@heroui/use-pagination@5811

@heroui/use-real-shape

npm i https://pkg.pr.new/@heroui/use-real-shape@5811

@heroui/use-ref-state

npm i https://pkg.pr.new/@heroui/use-ref-state@5811

@heroui/use-resize

npm i https://pkg.pr.new/@heroui/use-resize@5811

@heroui/use-safe-layout-effect

npm i https://pkg.pr.new/@heroui/use-safe-layout-effect@5811

@heroui/use-scroll-position

npm i https://pkg.pr.new/@heroui/use-scroll-position@5811

@heroui/use-ssr

npm i https://pkg.pr.new/@heroui/use-ssr@5811

@heroui/use-theme

npm i https://pkg.pr.new/@heroui/use-theme@5811

@heroui/use-update-effect

npm i https://pkg.pr.new/@heroui/use-update-effect@5811

@heroui/use-viewport-size

npm i https://pkg.pr.new/@heroui/use-viewport-size@5811

@heroui/aria-utils

npm i https://pkg.pr.new/@heroui/aria-utils@5811

@heroui/dom-animation

npm i https://pkg.pr.new/@heroui/dom-animation@5811

@heroui/framer-utils

npm i https://pkg.pr.new/@heroui/framer-utils@5811

@heroui/react-rsc-utils

npm i https://pkg.pr.new/@heroui/react-rsc-utils@5811

@heroui/react-utils

npm i https://pkg.pr.new/@heroui/react-utils@5811

@heroui/shared-icons

npm i https://pkg.pr.new/@heroui/shared-icons@5811

@heroui/shared-utils

npm i https://pkg.pr.new/@heroui/shared-utils@5811

@heroui/stories-utils

npm i https://pkg.pr.new/@heroui/stories-utils@5811

@heroui/test-utils

npm i https://pkg.pr.new/@heroui/test-utils@5811

commit: 15a3c05

Comment on lines +184 to +195
(props) =>
mergeProps(
tabListProps,
{
ref: domRef,
"data-slot": "tabList",
"aria-orientation": orientation,
className: slots.tabList({class: clsx(classNames?.tabList, props?.className)}),
},
props,
),
[domRef, tabListProps, classNames, slots, orientation],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to update this part. aria-orientation is already included in tabListProps. Also don't see the reason why we need to wrap everything in mergeProps.

"@heroui/tabs": patch
---

Fix vertical tabs to use correct aria-orientation and support Up/Down arrow navigation for accessibility.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please include the issue number at the end. i.e.

Fix vertical tabs to use correct aria-orientation and support Up/Down arrow navigation for accessibility. (#5810) 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] - Vertical Tabs use incorrect aria-orientation and do not respond to Up/Down arrow keys

2 participants