diff --git a/.changeset/honest-kings-occur.md b/.changeset/honest-kings-occur.md
new file mode 100644
index 00000000000..979fc736a5a
--- /dev/null
+++ b/.changeset/honest-kings-occur.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': patch
+---
+
+Tooltip v2: Allow external id to be passed down in tooltip so that the trigger can be labelled/described by the that id
diff --git a/src/drafts/Tooltip/Tooltip.examples.stories.tsx b/src/drafts/Tooltip/Tooltip.examples.stories.tsx
index a54a549a1bb..04ba0cf3526 100644
--- a/src/drafts/Tooltip/Tooltip.examples.stories.tsx
+++ b/src/drafts/Tooltip/Tooltip.examples.stories.tsx
@@ -11,6 +11,7 @@ import {
SmileyIcon,
EyeIcon,
CommentIcon,
+ XIcon,
} from '@primer/octicons-react'
import {default as VisuallyHidden} from '../../_VisuallyHidden'
@@ -19,6 +20,12 @@ export default {
component: Tooltip,
}
+export const CustomId = () => (
+
+ {}} />
+
+)
+
export const FilesPage = () => (
diff --git a/src/drafts/Tooltip/Tooltip.tsx b/src/drafts/Tooltip/Tooltip.tsx
index 949ea46c34b..f5dbd95b561 100644
--- a/src/drafts/Tooltip/Tooltip.tsx
+++ b/src/drafts/Tooltip/Tooltip.tsx
@@ -185,8 +185,8 @@ const isInteractive = (element: HTMLElement) => {
export const TooltipContext = React.createContext<{tooltipId?: string}>({})
export const Tooltip = React.forwardRef(
- ({direction = 's', text, type = 'description', children, ...rest}: TooltipProps, forwardedRef) => {
- const tooltipId = useId()
+ ({direction = 's', text, type = 'description', children, id, ...rest}: TooltipProps, forwardedRef) => {
+ const tooltipId = useId(id)
const child = Children.only(children)
const triggerRef = useProvidedRefOrCreate(forwardedRef as React.RefObject)
const tooltipElRef = useRef(null)
@@ -273,9 +273,9 @@ export const Tooltip = React.forwardRef(
React.cloneElement(child as React.ReactElement, {
ref: triggerRef,
// If it is a type description, we use tooltip to describe the trigger
- 'aria-describedby': type === 'description' ? `tooltip-${tooltipId}` : child.props['aria-describedby'],
+ 'aria-describedby': type === 'description' ? tooltipId : child.props['aria-describedby'],
// If it is a label type, we use tooltip to label the trigger
- 'aria-labelledby': type === 'label' ? `tooltip-${tooltipId}` : child.props['aria-labelledby'],
+ 'aria-labelledby': type === 'label' ? tooltipId : child.props['aria-labelledby'],
onBlur: (event: React.FocusEvent) => {
closeTooltip()
child.props.onBlur?.(event)
@@ -301,7 +301,7 @@ export const Tooltip = React.forwardRef(
role={type === 'description' ? 'tooltip' : undefined}
// stop AT from announcing the tooltip twice when it is a label type because it will be announced with "aria-labelledby"
aria-hidden={type === 'label' ? true : undefined}
- id={`tooltip-${tooltipId}`}
+ id={tooltipId}
// mouse leave and enter on the tooltip itself is needed to keep the tooltip open when the mouse is over the tooltip
onMouseEnter={openTooltip}
onMouseLeave={closeTooltip}
diff --git a/src/drafts/Tooltip/__tests__/Tooltip.test.tsx b/src/drafts/Tooltip/__tests__/Tooltip.test.tsx
index e8fbea212f3..c6ffa4ac8b5 100644
--- a/src/drafts/Tooltip/__tests__/Tooltip.test.tsx
+++ b/src/drafts/Tooltip/__tests__/Tooltip.test.tsx
@@ -3,7 +3,8 @@ import {Tooltip, TooltipProps} from '../Tooltip'
import {checkStoriesForAxeViolations} from '../../../utils/testing'
import {render as HTMLRender} from '@testing-library/react'
import theme from '../../../theme'
-import {Button, ActionMenu, ActionList, ThemeProvider, SSRProvider, BaseStyles} from '../../../'
+import {Button, IconButton, ActionMenu, ActionList, ThemeProvider, SSRProvider, BaseStyles} from '../../../'
+import {XIcon} from '@primer/octicons-react'
const TooltipComponent = (props: Omit & {text?: string}) => (
@@ -91,4 +92,22 @@ describe('Tooltip', () => {
expect(menuButton).toHaveAttribute('aria-describedby', tooltip.id)
expect(menuButton).toHaveAttribute('aria-haspopup', 'true')
})
+ it('should use the custom tooltip id (if present) to label the trigger element', () => {
+ const {getByRole} = HTMLRender(
+
+ {}} />
+ ,
+ )
+ const triggerEL = getByRole('button')
+ expect(triggerEL).toHaveAttribute('aria-labelledby', 'custom-tooltip-id')
+ })
+ it('should use the custom tooltip id (if present) to described the trigger element', () => {
+ const {getByRole} = HTMLRender(
+
+
+ ,
+ )
+ const triggerEL = getByRole('button')
+ expect(triggerEL).toHaveAttribute('aria-describedby', 'custom-tooltip-id')
+ })
})