From c5d2eb993a7341e01e06a34f01ef1c43c15815ec Mon Sep 17 00:00:00 2001 From: petdud Date: Sun, 2 Aug 2026 22:14:35 +0200 Subject: [PATCH 1/3] Change files --- ...react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json diff --git a/change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json b/change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json new file mode 100644 index 0000000000000..0d548174bf6f1 --- /dev/null +++ b/change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: add secondary content to Tooltip", + "packageName": "@fluentui/react-tooltip", + "email": "petrduda@microsoft.com", + "dependentChangeType": "patch" +} From 3734834ba305a72d41790e2a75f9cdc5548e1644 Mon Sep 17 00:00:00 2001 From: petdud Date: Sun, 2 Aug 2026 22:26:20 +0200 Subject: [PATCH 2/3] feat(react-tooltip): add secondary content Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d310308d-5b2c-46e6-ac1a-e5a717893d8f --- .../src/stories/Tooltip/Tooltip.stories.tsx | 16 +++++++ ...-d906f048-c7cf-41ab-bb86-27edec248fed.json | 2 +- .../library/etc/react-tooltip.api.md | 3 ++ .../src/components/Tooltip/Tooltip.test.tsx | 39 +++++++++++++++++ .../src/components/Tooltip/Tooltip.types.ts | 15 +++++++ .../src/components/Tooltip/renderTooltip.tsx | 9 +++- .../src/components/Tooltip/useTooltipBase.tsx | 5 +++ .../Tooltip/useTooltipStyles.styles.ts | 42 +++++++++++++++++++ .../src/Tooltip/TooltipAccessibility.md | 2 + .../TooltipSecondaryContent.stories.tsx | 20 +++++++++ .../stories/src/Tooltip/index.stories.tsx | 1 + 11 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 packages/react-components/react-tooltip/stories/src/Tooltip/TooltipSecondaryContent.stories.tsx diff --git a/apps/vr-tests-react-components/src/stories/Tooltip/Tooltip.stories.tsx b/apps/vr-tests-react-components/src/stories/Tooltip/Tooltip.stories.tsx index ce840bb11acef..22ed0fc425b54 100644 --- a/apps/vr-tests-react-components/src/stories/Tooltip/Tooltip.stories.tsx +++ b/apps/vr-tests-react-components/src/stories/Tooltip/Tooltip.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import type { Meta } from '@storybook/react-webpack5'; import { Tooltip } from '@fluentui/react-tooltip'; +import { TextBoldRegular } from '@fluentui/react-icons'; import { useStyles } from './utils'; import { DARK_MODE, getStoryVariant, HIGH_CONTRAST, TestWrapperDecorator } from '../../utilities'; @@ -29,6 +30,21 @@ export const BasicDarkMode = getStoryVariant(Basic, DARK_MODE); export const BasicHighContrast = getStoryVariant(Basic, HIGH_CONTRAST); +export const SecondaryContent = () => ( +
+ + + +
+); +SecondaryContent.storyName = 'secondary content'; + +export const SecondaryContentDarkMode = getStoryVariant(SecondaryContent, DARK_MODE); + +export const SecondaryContentHighContrast = getStoryVariant(SecondaryContent, HIGH_CONTRAST); + export const Inverted = () => (
diff --git a/change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json b/change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json index 0d548174bf6f1..8def9b2b399ea 100644 --- a/change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json +++ b/change/@fluentui-react-tooltip-d906f048-c7cf-41ab-bb86-27edec248fed.json @@ -1,5 +1,5 @@ { - "type": "patch", + "type": "minor", "comment": "feat: add secondary content to Tooltip", "packageName": "@fluentui/react-tooltip", "email": "petrduda@microsoft.com", diff --git a/packages/react-components/react-tooltip/library/etc/react-tooltip.api.md b/packages/react-components/react-tooltip/library/etc/react-tooltip.api.md index 902ae0bd236b0..fde29a8cbdfe9 100644 --- a/packages/react-components/react-tooltip/library/etc/react-tooltip.api.md +++ b/packages/react-components/react-tooltip/library/etc/react-tooltip.api.md @@ -50,6 +50,7 @@ export type TooltipProps = ComponentProps & TriggerProps>; + secondaryContent?: Slot<'span'>; }; // @public @@ -59,6 +60,8 @@ export type TooltipState = ComponentState & Pick; arrowClassName?: string; + contentLayoutClassName?: string; + primaryContentClassName?: string; }; // @public diff --git a/packages/react-components/react-tooltip/library/src/components/Tooltip/Tooltip.test.tsx b/packages/react-components/react-tooltip/library/src/components/Tooltip/Tooltip.test.tsx index 1c74c0f9b3035..7faded259be6f 100644 --- a/packages/react-components/react-tooltip/library/src/components/Tooltip/Tooltip.test.tsx +++ b/packages/react-components/react-tooltip/library/src/components/Tooltip/Tooltip.test.tsx @@ -44,6 +44,13 @@ describe('Tooltip', () => { 'consistent-callback-args': { legacyCallbacks: ['onVisibleChange'], }, + 'has-static-classnames': [ + { + props: { + secondaryContent: 'Test secondary content', + }, + }, + ], }, }); @@ -88,6 +95,38 @@ describe('Tooltip', () => { expect(target.getAttribute('aria-labelledby')).toBe('the-tooltip-id'); }); + it('renders secondary content without including it in the accessible label', () => { + const result = render( + + diff --git a/packages/react-components/react-headless-components-preview/library/src/components/Tooltip/Tooltip.test.tsx b/packages/react-components/react-headless-components-preview/library/src/components/Tooltip/Tooltip.test.tsx index 5137f1b26b42c..6e42a086bd6a9 100644 --- a/packages/react-components/react-headless-components-preview/library/src/components/Tooltip/Tooltip.test.tsx +++ b/packages/react-components/react-headless-components-preview/library/src/components/Tooltip/Tooltip.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; import { render, screen } from '@testing-library/react'; -import { resetIdsForTests } from '@fluentui/react-utilities'; +import { resetIdsForTests, SSRProvider } from '@fluentui/react-utilities'; import { isConformant } from '../../testing/isConformant'; import type { IsConformantOptions } from '@fluentui/react-conformance'; import { Tooltip } from './Tooltip'; @@ -100,6 +101,80 @@ describe('Tooltip', () => { expect(target).toHaveAttribute('aria-describedby', tooltip.id); }); + it('renders secondary content', () => { + render( + + + , + ); + + const tooltip = screen.getByRole('tooltip'); + const target = screen.getByRole('button'); + expect(tooltip).toHaveTextContent('BoldCtrl+B'); + expect(tooltip.querySelector('span')).toHaveTextContent('Ctrl+B'); + expect(target).toHaveAttribute('aria-label', 'Bold Ctrl+B'); + expect(target).not.toHaveAttribute('aria-labelledby'); + }); + + it('includes secondary content in a rich accessible label', () => { + render( + Bold} secondaryContent="Ctrl+B" relationship="label"> + + , + ); + + const tooltip = screen.getByRole('tooltip'); + const target = screen.getByRole('button'); + const secondaryContent = tooltip.querySelector(':scope > span'); + + expect(target).toHaveAttribute('aria-labelledby', tooltip.id); + expect(secondaryContent).not.toHaveAttribute('aria-hidden'); + }); + + it('keeps a string label with secondary content when the trigger popup is expanded', () => { + render( + + + , + ); + + expect(screen.queryByRole('tooltip')).toBeNull(); + expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Bold Ctrl+B'); + }); + + it('keeps a generic secondary content label when the trigger popup is expanded', () => { + render( + Ctrl+B} relationship="label" visible> + + , + ); + + const tooltip = document.querySelector('[role="tooltip"]'); + const target = screen.getByRole('button'); + + expect(tooltip).not.toBeNull(); + expect(target).not.toHaveAttribute('aria-label'); + expect(target).toHaveAttribute('aria-labelledby', tooltip?.id); + expect(tooltip).toHaveTextContent('BoldCtrl+B'); + }); + + it('keeps the primary string label during SSR with generic secondary content', () => { + const html = renderToStaticMarkup( + + Ctrl+B} relationship="label"> +