From ead88e21f76cc759807306d083ed829b604b4a84 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Mar 2026 18:17:55 +0000
Subject: [PATCH 1/7] Initial plan
From 194087cfe56cd62f2132423300f9472e5494160f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Mar 2026 18:35:05 +0000
Subject: [PATCH 2/7] Support array of strings for keybindingHint on IconButton
Co-authored-by: iansan5653 <2294248+iansan5653@users.noreply.github.com>
---
.../Button/IconButton.features.stories.tsx | 4 ++++
packages/react/src/Button/types.ts | 2 +-
packages/react/src/TooltipV2/Tooltip.tsx | 24 +++++++++++++++----
.../src/TooltipV2/__tests__/Tooltip.test.tsx | 13 ++++++++++
4 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx
index 937ad43bb45..27147337b90 100644
--- a/packages/react/src/Button/IconButton.features.stories.tsx
+++ b/packages/react/src/Button/IconButton.features.stories.tsx
@@ -88,6 +88,10 @@ export const KeybindingHintOnDescription = () => (
export const KeybindingHint = () =>
+export const MultipleKeybindingHints = () => (
+
+)
+
export const LongDelayedTooltip = () => (
// Ideal for cases where we don't want to show the tooltip immediately — for example, when the user is just passing over the element.
diff --git a/packages/react/src/Button/types.ts b/packages/react/src/Button/types.ts
index 6870562b516..acdd7190527 100644
--- a/packages/react/src/Button/types.ts
+++ b/packages/react/src/Button/types.ts
@@ -91,7 +91,7 @@ export type IconButtonProps = ButtonA11yProps & {
tooltipDirection?: TooltipDirection
/** @deprecated Use `keybindingHint` instead. */
keyshortcuts?: string
- keybindingHint?: string
+ keybindingHint?: string | string[]
} & Omit
// adopted from React.AnchorHTMLAttributes
diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx
index 92f0599da98..6dd3cec9a17 100644
--- a/packages/react/src/TooltipV2/Tooltip.tsx
+++ b/packages/react/src/TooltipV2/Tooltip.tsx
@@ -17,7 +17,7 @@ export type TooltipProps = React.PropsWithChildren<{
direction?: TooltipDirection
text: string
type?: 'label' | 'description'
- keybindingHint?: KeybindingHintProps['keys']
+ keybindingHint?: KeybindingHintProps['keys'] | Array
/**
* Delay in milliseconds before showing the tooltip
* @default short (50ms)
@@ -273,6 +273,13 @@ export const Tooltip: ForwardRefExoticComponent<
const isMacOS = useIsMacOS()
const hasAriaLabel = 'aria-label' in rest
+ // Normalize keybindingHint to an array for uniform rendering
+ const keybindingHints = keybindingHint
+ ? Array.isArray(keybindingHint)
+ ? keybindingHint
+ : [keybindingHint]
+ : undefined
+
return (
<>
@@ -353,9 +360,9 @@ export const Tooltip: ForwardRefExoticComponent<
onMouseEnter={openTooltip}
onMouseLeave={closeTooltip}
// If there is an aria-label prop, always assign the ID to the parent so the accessible label can be overridden
- id={hasAriaLabel || !keybindingHint ? tooltipId : undefined}
+ id={hasAriaLabel || !keybindingHints ? tooltipId : undefined}
>
- {keybindingHint ? (
+ {keybindingHints ? (
<>
{text}
@@ -364,10 +371,17 @@ export const Tooltip: ForwardRefExoticComponent<
and renders full key names as `VisuallyHidden` text. Due to the browser bug this causes the label text
to duplicate the symbols and key names. To work around this, we exclude the hint from being part of the
label and instead render the plain keybinding description string. */}
- ({getAccessibleKeybindingHintString(keybindingHint, isMacOS)})
+
+ ({keybindingHints.map(hint => getAccessibleKeybindingHintString(hint, isMacOS)).join(' or ')})
+
-
+ {keybindingHints.map((hint, i) => (
+
+ {i > 0 && ' or '}
+
+
+ ))}
>
) : (
diff --git a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx
index 8960e76f7a5..bafe366b9dd 100644
--- a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx
+++ b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx
@@ -172,6 +172,19 @@ describe('Tooltip', () => {
)
expect(getByRole('button', {name: 'Overridden label'})).toBeInTheDocument()
})
+ it('includes multiple keybinding hints joined with "or" in the label text', () => {
+ const {getByRole} = HTMLRender()
+ expect(getByRole('button', {name: 'Tooltip text (control k or control shift k)'})).toBeInTheDocument()
+ })
+ it('renders multiple keybinding hints when an array is provided', () => {
+ const {getAllByTestId, container} = HTMLRender(
+ ,
+ )
+ expect(getAllByTestId('keybinding-hint')).toHaveLength(2)
+ // Verify the "or" separator is rendered between keybinding hints
+ const hintContainer = container.querySelector('[aria-hidden="true"] [aria-hidden="true"]')
+ expect(hintContainer?.textContent).toContain(' or ')
+ })
it('should append tooltip id to existing aria-describedby value on the trigger element', () => {
const {getByRole, getByText} = HTMLRender()
From 6818499f0fe1f7dd293bdc1b34334e8b136ee871 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Mar 2026 19:58:51 +0000
Subject: [PATCH 3/7] Treat empty array keybindingHint as undefined
Co-authored-by: iansan5653 <2294248+iansan5653@users.noreply.github.com>
---
packages/react/src/TooltipV2/Tooltip.tsx | 6 ++++--
packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx | 4 ++++
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx
index 6dd3cec9a17..96b7b8b0943 100644
--- a/packages/react/src/TooltipV2/Tooltip.tsx
+++ b/packages/react/src/TooltipV2/Tooltip.tsx
@@ -273,10 +273,12 @@ export const Tooltip: ForwardRefExoticComponent<
const isMacOS = useIsMacOS()
const hasAriaLabel = 'aria-label' in rest
- // Normalize keybindingHint to an array for uniform rendering
+ // Normalize keybindingHint to an array for uniform rendering, treating empty arrays as undefined
const keybindingHints = keybindingHint
? Array.isArray(keybindingHint)
- ? keybindingHint
+ ? keybindingHint.length > 0
+ ? keybindingHint
+ : undefined
: [keybindingHint]
: undefined
diff --git a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx
index bafe366b9dd..9d914c6b007 100644
--- a/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx
+++ b/packages/react/src/TooltipV2/__tests__/Tooltip.test.tsx
@@ -185,6 +185,10 @@ describe('Tooltip', () => {
const hintContainer = container.querySelector('[aria-hidden="true"] [aria-hidden="true"]')
expect(hintContainer?.textContent).toContain(' or ')
})
+ it('treats an empty array keybindingHint as if no hint was provided', () => {
+ const {queryByTestId} = HTMLRender()
+ expect(queryByTestId('keybinding-hint')).not.toBeInTheDocument()
+ })
it('should append tooltip id to existing aria-describedby value on the trigger element', () => {
const {getByRole, getByText} = HTMLRender()
From d83f1db01ebb81a484f5a75920b4294ff2b20cf6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Mar 2026 19:59:17 +0000
Subject: [PATCH 4/7] Add changeset for keybindingHint array support
Co-authored-by: iansan5653 <2294248+iansan5653@users.noreply.github.com>
---
.changeset/iconbutton-keybinding-hint-array.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .changeset/iconbutton-keybinding-hint-array.md
diff --git a/.changeset/iconbutton-keybinding-hint-array.md b/.changeset/iconbutton-keybinding-hint-array.md
new file mode 100644
index 00000000000..9c8126df9c1
--- /dev/null
+++ b/.changeset/iconbutton-keybinding-hint-array.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": minor
+---
+
+`IconButton`: `keybindingHint` now accepts `string[]` in addition to `string`. Multiple hints are rendered joined with "or".
From d0bf44674c4652aafc40f074a60606824839cc9a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 4 Mar 2026 21:35:34 +0000
Subject: [PATCH 5/7] Simplify keybindingHints normalization and add extra
spacing for multiple hints
Co-authored-by: iansan5653 <2294248+iansan5653@users.noreply.github.com>
---
.../react/src/TooltipV2/Tooltip.module.css | 4 ++++
packages/react/src/TooltipV2/Tooltip.tsx | 23 ++++++++++---------
2 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/packages/react/src/TooltipV2/Tooltip.module.css b/packages/react/src/TooltipV2/Tooltip.module.css
index 0c51f0a5cae..d95789c1299 100644
--- a/packages/react/src/TooltipV2/Tooltip.module.css
+++ b/packages/react/src/TooltipV2/Tooltip.module.css
@@ -126,3 +126,7 @@
.KeybindingHintContainer.HasTextBefore {
margin-left: var(--base-size-6);
}
+
+.KeybindingHintContainer.HasTextBefore.HasMultipleHints {
+ margin-left: var(--base-size-8);
+}
diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx
index 96b7b8b0943..e33a6d19669 100644
--- a/packages/react/src/TooltipV2/Tooltip.tsx
+++ b/packages/react/src/TooltipV2/Tooltip.tsx
@@ -273,14 +273,8 @@ export const Tooltip: ForwardRefExoticComponent<
const isMacOS = useIsMacOS()
const hasAriaLabel = 'aria-label' in rest
- // Normalize keybindingHint to an array for uniform rendering, treating empty arrays as undefined
- const keybindingHints = keybindingHint
- ? Array.isArray(keybindingHint)
- ? keybindingHint.length > 0
- ? keybindingHint
- : undefined
- : [keybindingHint]
- : undefined
+ // Normalize keybindingHint to an array for uniform rendering
+ const keybindingHints = keybindingHint ? (Array.isArray(keybindingHint) ? keybindingHint : [keybindingHint]) : []
return (
@@ -362,9 +356,9 @@ export const Tooltip: ForwardRefExoticComponent<
onMouseEnter={openTooltip}
onMouseLeave={closeTooltip}
// If there is an aria-label prop, always assign the ID to the parent so the accessible label can be overridden
- id={hasAriaLabel || !keybindingHints ? tooltipId : undefined}
+ id={hasAriaLabel || keybindingHints.length === 0 ? tooltipId : undefined}
>
- {keybindingHints ? (
+ {keybindingHints.length > 0 ? (
<>
{text}
@@ -377,7 +371,14 @@ export const Tooltip: ForwardRefExoticComponent<
({keybindingHints.map(hint => getAccessibleKeybindingHintString(hint, isMacOS)).join(' or ')})
-
+ 1 && classes.HasMultipleHints,
+ )}
+ aria-hidden
+ >
{keybindingHints.map((hint, i) => (
{i > 0 && ' or '}
From 464c69dd39241b49bc6110d59b3d7cedcc5b0806 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Mar 2026 03:28:10 +0000
Subject: [PATCH 6/7] Update keybindingHint type in docs.json files and
simplify normalization with default prop
Co-authored-by: francinelucca <40550942+francinelucca@users.noreply.github.com>
---
packages/react/src/Button/IconButton.docs.json | 4 ++--
packages/react/src/TooltipV2/Tooltip.docs.json | 4 ++--
packages/react/src/TooltipV2/Tooltip.tsx | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/packages/react/src/Button/IconButton.docs.json b/packages/react/src/Button/IconButton.docs.json
index 58ec3573306..4af4062608e 100644
--- a/packages/react/src/Button/IconButton.docs.json
+++ b/packages/react/src/Button/IconButton.docs.json
@@ -107,8 +107,8 @@
},
{
"name": "keybindingHint",
- "type": "string",
- "description": "Optional keybinding hint to show in the tooltip for this button. See the `KeybindingHint` component documentation for the correct format for this string. Has no effect if tooltip is overridden or disabled. Does **not** bind any keybindings for this button - this is only for visual hints."
+ "type": "string | string[]",
+ "description": "Optional keybinding hint to show in the tooltip for this button. Pass a string for a single shortcut or an array of strings to show multiple shortcuts joined with \"or\". See the `KeybindingHint` component documentation for the correct format. Has no effect if tooltip is overridden or disabled. Does **not** bind any keybindings for this button - this is only for visual hints."
},
{
"name": "tooltipDirection",
diff --git a/packages/react/src/TooltipV2/Tooltip.docs.json b/packages/react/src/TooltipV2/Tooltip.docs.json
index 5b56bc7d269..019128ada7d 100644
--- a/packages/react/src/TooltipV2/Tooltip.docs.json
+++ b/packages/react/src/TooltipV2/Tooltip.docs.json
@@ -55,8 +55,8 @@
},
{
"name": "keybindingHint",
- "type": "string",
- "description": "Optional keybinding hint to indicate the availability of a keyboard shortcut. Supported syntax is described in the docs for the `KeybindingHint` component."
+ "type": "string | string[]",
+ "description": "Optional keybinding hint to indicate the availability of a keyboard shortcut. Pass a string for a single shortcut or an array of strings to show multiple shortcuts joined with \"or\". Supported syntax is described in the docs for the `KeybindingHint` component."
},
{
"name": "delay",
diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx
index e33a6d19669..108ecfd8927 100644
--- a/packages/react/src/TooltipV2/Tooltip.tsx
+++ b/packages/react/src/TooltipV2/Tooltip.tsx
@@ -115,7 +115,7 @@ export const Tooltip: ForwardRefExoticComponent<
children,
id,
className,
- keybindingHint,
+ keybindingHint = [],
delay = 'short',
_privateDisableTooltip = false,
...rest
@@ -274,7 +274,7 @@ export const Tooltip: ForwardRefExoticComponent<
const hasAriaLabel = 'aria-label' in rest
// Normalize keybindingHint to an array for uniform rendering
- const keybindingHints = keybindingHint ? (Array.isArray(keybindingHint) ? keybindingHint : [keybindingHint]) : []
+ const keybindingHints = Array.isArray(keybindingHint) ? keybindingHint : [keybindingHint]
return (
From c1bcdf4a443ac1491a144d69d80d780a0041a916 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Mar 2026 14:50:03 +0000
Subject: [PATCH 7/7] Extract empty keybindingHint default to module-level
constant for stable reference
Co-authored-by: iansan5653 <2294248+iansan5653@users.noreply.github.com>
---
packages/react/src/TooltipV2/Tooltip.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/react/src/TooltipV2/Tooltip.tsx b/packages/react/src/TooltipV2/Tooltip.tsx
index 108ecfd8927..1f832704a1f 100644
--- a/packages/react/src/TooltipV2/Tooltip.tsx
+++ b/packages/react/src/TooltipV2/Tooltip.tsx
@@ -103,6 +103,8 @@ const isInteractive = (element: HTMLElement) => {
}
export const TooltipContext = React.createContext<{tooltipId?: string}>({})
+const emptyKeybindingHints: Array = []
+
export const Tooltip: ForwardRefExoticComponent<
React.PropsWithoutRef & React.RefAttributes
> &
@@ -115,7 +117,7 @@ export const Tooltip: ForwardRefExoticComponent<
children,
id,
className,
- keybindingHint = [],
+ keybindingHint = emptyKeybindingHints,
delay = 'short',
_privateDisableTooltip = false,
...rest