From 6877b4f3a5e9ddf706ed7da7cc8f16430eede5ef Mon Sep 17 00:00:00 2001 From: yiheng-kkk <272397091+yiheng-kkk@users.noreply.github.com> Date: Sun, 23 Aug 2026 17:40:19 +0800 Subject: [PATCH 1/2] fix: keep auxiliary controls outside tablist --- src/TabNavList/index.tsx | 13 ++++++++-- tests/__snapshots__/index.test.tsx.snap | 14 +++++++---- tests/accessibility.test.tsx | 32 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/TabNavList/index.tsx b/src/TabNavList/index.tsx index 25a7e05d..128031df 100644 --- a/src/TabNavList/index.tsx +++ b/src/TabNavList/index.tsx @@ -565,6 +565,7 @@ const TabNavList = React.forwardRef((props, ref // ========================= Render ======================== const hasDropdown = !!hiddenTabs.length; + const tabIds = id ? tabs.map(tab => `${id}-tab-${tab.key}`).join(' ') : undefined; const wrapPrefix = `${prefixCls}-nav-wrap`; let pingLeft: boolean; let pingRight: boolean; @@ -588,8 +589,8 @@ const TabNavList = React.forwardRef((props, ref
{ @@ -597,6 +598,14 @@ const TabNavList = React.forwardRef((props, ref doLockAnimation(); }} > + {tabIds && ( +
+ )} + diff --git a/tests/__snapshots__/index.test.tsx.snap b/tests/__snapshots__/index.test.tsx.snap index 43e22036..00dbcb76 100644 --- a/tests/__snapshots__/index.test.tsx.snap +++ b/tests/__snapshots__/index.test.tsx.snap @@ -5,10 +5,13 @@ exports[`Tabs.Basic Normal 1`] = ` class="rc-tabs rc-tabs-top" >
+
@@ -108,10 +111,13 @@ exports[`Tabs.Basic Skip invalidate children 1`] = ` class="rc-tabs rc-tabs-top" >
+
diff --git a/tests/accessibility.test.tsx b/tests/accessibility.test.tsx index 385d889c..0d029a9a 100644 --- a/tests/accessibility.test.tsx +++ b/tests/accessibility.test.tsx @@ -1,6 +1,7 @@ import { render, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { renderToString } from 'react-dom/server'; import type { TabsProps } from '../src'; import Tabs from '../src'; @@ -35,6 +36,37 @@ describe('Tabs.Accessibility', () => { /> ); + it('should keep tablist semantics during server rendering', () => { + const html = renderToString(createTabs()); + + expect(html).toContain('role="tablist"'); + expect(html).toContain('aria-orientation="horizontal"'); + }); + + it('should keep auxiliary controls outside the semantic tablist', () => { + const { getByRole, getAllByRole } = render( + createTabs({ + tabBarExtraContent: , + editable: { + onEdit: jest.fn(), + }, + }), + ); + + const tablist = getByRole('tablist'); + const tabs = getAllByRole('tab'); + + expect(tablist).toBeEmptyDOMElement(); + expect(tablist).toHaveAttribute('aria-owns', tabs.map(tab => tab.id).join(' ')); + expect(tablist).not.toContainElement(getByRole('button', { name: 'Extra action' })); + getAllByRole('button', { name: 'Add tab' }).forEach(button => { + expect(tablist).not.toContainElement(button); + }); + getAllByRole('button', { name: 'remove' }).forEach(button => { + expect(tablist).not.toContainElement(button); + }); + }); + it('should support keyboard navigation', async () => { const user = userEvent.setup(); const { getByRole } = render(createTabs()); From f33c401d7c5b604d938657ee349f5f623a3491d2 Mon Sep 17 00:00:00 2001 From: yiheng-kkk <272397091+yiheng-kkk@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:09:35 +0800 Subject: [PATCH 2/2] fix: handle empty tab lists --- src/TabNavList/index.tsx | 13 ++++++++----- tests/accessibility.test.tsx | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/TabNavList/index.tsx b/src/TabNavList/index.tsx index 128031df..9599da3a 100644 --- a/src/TabNavList/index.tsx +++ b/src/TabNavList/index.tsx @@ -565,7 +565,8 @@ const TabNavList = React.forwardRef((props, ref // ========================= Render ======================== const hasDropdown = !!hiddenTabs.length; - const tabIds = id ? tabs.map(tab => `${id}-tab-${tab.key}`).join(' ') : undefined; + const hasTabList = Boolean(id); + const tabIds = hasTabList ? tabs.map(tab => `${id}-tab-${tab.key}`).join(' ') : undefined; const wrapPrefix = `${prefixCls}-nav-wrap`; let pingLeft: boolean; let pingRight: boolean; @@ -589,8 +590,10 @@ const TabNavList = React.forwardRef((props, ref
{ @@ -598,11 +601,11 @@ const TabNavList = React.forwardRef((props, ref doLockAnimation(); }} > - {tabIds && ( + {hasTabList && (
)} diff --git a/tests/accessibility.test.tsx b/tests/accessibility.test.tsx index 0d029a9a..548ff452 100644 --- a/tests/accessibility.test.tsx +++ b/tests/accessibility.test.tsx @@ -67,6 +67,25 @@ describe('Tabs.Accessibility', () => { }); }); + it('should keep auxiliary controls outside an empty semantic tablist', () => { + const { getByRole, getAllByRole } = render( + Extra action} + editable={{ onEdit: jest.fn() }} + />, + ); + + const tablist = getByRole('tablist'); + + expect(tablist).toBeEmptyDOMElement(); + expect(tablist).not.toHaveAttribute('aria-owns'); + expect(tablist).not.toContainElement(getByRole('button', { name: 'Extra action' })); + getAllByRole('button', { name: 'Add tab' }).forEach(button => { + expect(tablist).not.toContainElement(button); + }); + }); + it('should support keyboard navigation', async () => { const user = userEvent.setup(); const { getByRole } = render(createTabs());