diff --git a/.changeset/metal-cycles-appear.md b/.changeset/metal-cycles-appear.md
new file mode 100644
index 00000000000..5f6a4cf64cb
--- /dev/null
+++ b/.changeset/metal-cycles-appear.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": minor
+---
+
+IconButton: Add `keyshortcuts` prop to allow labelling and describing support for keyboard shortcut (through tooltips)
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-colorblind-linux.png
new file mode 100644
index 00000000000..a948549ddd2
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-dimmed-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-dimmed-linux.png
new file mode 100644
index 00000000000..92621ebc4a8
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-dimmed-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-high-contrast-linux.png
new file mode 100644
index 00000000000..45ba9851146
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-linux.png
new file mode 100644
index 00000000000..a948549ddd2
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-tritanopia-linux.png
new file mode 100644
index 00000000000..a948549ddd2
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-dark-tritanopia-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-colorblind-linux.png
new file mode 100644
index 00000000000..294988f9740
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-high-contrast-linux.png
new file mode 100644
index 00000000000..dab1a2bb5ea
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-linux.png
new file mode 100644
index 00000000000..294988f9740
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-tritanopia-linux.png
new file mode 100644
index 00000000000..294988f9740
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-light-tritanopia-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-colorblind-linux.png
new file mode 100644
index 00000000000..a9df5acaeb1
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-dimmed-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-dimmed-linux.png
new file mode 100644
index 00000000000..91b18e01673
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-dimmed-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-high-contrast-linux.png
new file mode 100644
index 00000000000..ab00467787c
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-linux.png
new file mode 100644
index 00000000000..a9df5acaeb1
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-tritanopia-linux.png
new file mode 100644
index 00000000000..a9df5acaeb1
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-dark-tritanopia-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-colorblind-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-colorblind-linux.png
new file mode 100644
index 00000000000..318d5545c21
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-colorblind-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-high-contrast-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-high-contrast-linux.png
new file mode 100644
index 00000000000..bdd1e750e45
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-high-contrast-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-linux.png
new file mode 100644
index 00000000000..318d5545c21
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-linux.png differ
diff --git a/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-tritanopia-linux.png b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-tritanopia-linux.png
new file mode 100644
index 00000000000..318d5545c21
Binary files /dev/null and b/.playwright/snapshots/components/IconButton.test.ts-snapshots/IconButton-Keyshortcuts-on-Description-light-tritanopia-linux.png differ
diff --git a/e2e/components/IconButton.test.ts b/e2e/components/IconButton.test.ts
index 8b1e3aac1e6..86a6b3d1c26 100644
--- a/e2e/components/IconButton.test.ts
+++ b/e2e/components/IconButton.test.ts
@@ -308,4 +308,67 @@ test.describe('IconButton', () => {
})
}
})
+ test.describe('Keyshortcuts', () => {
+ for (const theme of themes) {
+ test.describe(theme, () => {
+ test('default @vrt', async ({page}) => {
+ await visit(page, {
+ id: 'components-iconbutton-features--keyshortcuts',
+ globals: {
+ colorScheme: theme,
+ },
+ })
+
+ // Default state
+ await page.keyboard.press('Tab') // focus on icon button
+ expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
+ `IconButton.Keyshortcuts.${theme}.png`,
+ )
+ })
+
+ test('axe @aat', async ({page}) => {
+ await visit(page, {
+ id: 'components-iconbutton-features--keyshortcuts',
+ globals: {
+ colorScheme: theme,
+ },
+ })
+ await page.keyboard.press('Tab') // focus on icon button
+ await expect(page).toHaveNoViolations()
+ })
+ })
+ }
+ })
+
+ test.describe('Keyshortcuts on Description', () => {
+ for (const theme of themes) {
+ test.describe(theme, () => {
+ test('default @vrt', async ({page}) => {
+ await visit(page, {
+ id: 'components-iconbutton-features--keyshortcuts-on-description',
+ globals: {
+ colorScheme: theme,
+ },
+ })
+
+ // Default state
+ await page.keyboard.press('Tab') // focus on icon button
+ expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
+ `IconButton.Keyshortcuts on Description.${theme}.png`,
+ )
+ })
+
+ test('axe @aat', async ({page}) => {
+ await visit(page, {
+ id: 'components-iconbutton-features--keyshortcuts-on-description',
+ globals: {
+ colorScheme: theme,
+ },
+ })
+ await page.keyboard.press('Tab') // focus on icon button
+ await expect(page).toHaveNoViolations()
+ })
+ })
+ }
+ })
})
diff --git a/packages/react/src/Button/IconButton.docs.json b/packages/react/src/Button/IconButton.docs.json
index bf8978b87f0..d460a90835a 100644
--- a/packages/react/src/Button/IconButton.docs.json
+++ b/packages/react/src/Button/IconButton.docs.json
@@ -42,10 +42,17 @@
"defaultValue": "",
"description": "Use an aria label to describe the functionality of the button. Please refer to [our guidance on alt text](https://primer.style/guides/accessibility/alternative-text-for-images) for tips on writing good alternative text."
},
+ {
+ "name": "keyshortcuts",
+ "type": "string",
+ "defaultValue": "",
+ "required": false,
+ "description": "Keyboard shortcuts that trigger the button. Keyboard shortcut will be appended to the accessible name or description (depending on the tooltip type) of the button with a comma (i.e. Bold, Command+B) and it will be displayed in the tooltip."
+ },
{
"name": "sx",
"type": "SystemStyleObject"
}
],
"subcomponents": []
-}
\ No newline at end of file
+}
diff --git a/packages/react/src/Button/IconButton.features.stories.tsx b/packages/react/src/Button/IconButton.features.stories.tsx
index dc81b62bdfa..745eb362574 100644
--- a/packages/react/src/Button/IconButton.features.stories.tsx
+++ b/packages/react/src/Button/IconButton.features.stories.tsx
@@ -1,4 +1,4 @@
-import {HeartIcon, InboxIcon, ChevronDownIcon} from '@primer/octicons-react'
+import {HeartIcon, InboxIcon, ChevronDownIcon, BoldIcon} from '@primer/octicons-react'
import React from 'react'
import {IconButton} from '.'
import {ActionMenu} from '../ActionMenu'
@@ -88,3 +88,17 @@ export const AsAMenuAnchor = () => (
)
+
+export const KeyshortcutsOnDescription = () => (
+
+)
+
+export const Keyshortcuts = () => (
+
+)
diff --git a/packages/react/src/Button/IconButton.tsx b/packages/react/src/Button/IconButton.tsx
index e2ae3db8108..79a8b4d8ae3 100644
--- a/packages/react/src/Button/IconButton.tsx
+++ b/packages/react/src/Button/IconButton.tsx
@@ -18,6 +18,7 @@ const IconButton = forwardRef(
tooltipDirection,
// This is planned to be a temporary prop until the default tooltip on icon buttons are fully rolled out.
unsafeDisableTooltip = false,
+ keyshortcuts,
...props
},
forwardedRef,
@@ -53,10 +54,13 @@ const IconButton = forwardRef(
/>
)
} else {
+ // Does it have keyshortcuts?
+ const tooltipSuffix = keyshortcuts ? `, ${keyshortcuts}` : ''
+ const tooltipText = description ?? ariaLabel
return (
@@ -65,6 +69,7 @@ const IconButton = forwardRef(
data-component="IconButton"
sx={sxStyles}
type="button"
+ aria-keyshortcuts={keyshortcuts ?? undefined}
// If description is provided, we will use the tooltip to describe the button, so we need to keep the aria-label to label the button.
aria-label={description ? ariaLabel : undefined}
{...props}
diff --git a/packages/react/src/Button/__tests__/Button.test.tsx b/packages/react/src/Button/__tests__/Button.test.tsx
index 87b4e6b3b06..882e5f1cdce 100644
--- a/packages/react/src/Button/__tests__/Button.test.tsx
+++ b/packages/react/src/Button/__tests__/Button.test.tsx
@@ -138,4 +138,49 @@ describe('Button', () => {
expect(triggerEl).toHaveAttribute('aria-labelledby', tooltipEl.id)
expect(triggerEl).not.toHaveAttribute('aria-label')
})
+ it('should render aria-keyshorts on an icon button when keyshortcuts prop is passed', () => {
+ const {getByRole} = render(
+ ,
+ )
+ const triggerEl = getByRole('button')
+ expect(triggerEl).toHaveAttribute('aria-keyshortcuts', 'Command+H')
+ })
+ it('should append the keyshortcuts to the tooltip text that labels the icon button when keyshortcuts prop is passed', () => {
+ const {getByRole, getByText} = render(
+ ,
+ )
+ const triggerEl = getByRole('button')
+ const tooltipEl = getByText('Heart, Command+H')
+ expect(tooltipEl).toBeInTheDocument()
+ expect(triggerEl).toHaveAttribute('aria-labelledby', tooltipEl.id)
+ })
+ it('should render aria-keyshorts on an icon button when keyshortcuts prop is passed (Description Type)', () => {
+ const {getByRole, getByText} = render(
+ ,
+ )
+ const triggerEl = getByRole('button')
+ const tooltipEl = getByText('Love is all around, Command+H')
+ expect(triggerEl).toHaveAttribute('aria-describedby', tooltipEl.id)
+ })
+ it('should append the keyshortcuts to the tooltip text that describes the icon button when keyshortcuts prop is passed (Description Type)', () => {
+ const {getByRole, getByText} = render(
+ ,
+ )
+ const triggerEl = getByRole('button')
+ const tooltipEl = getByText('Love is all around, Command+H')
+ expect(tooltipEl).toBeInTheDocument()
+ expect(triggerEl).toHaveAttribute('aria-describedby', tooltipEl.id)
+ })
})
diff --git a/packages/react/src/Button/types.ts b/packages/react/src/Button/types.ts
index f1552d8b474..1efe6cb026a 100644
--- a/packages/react/src/Button/types.ts
+++ b/packages/react/src/Button/types.ts
@@ -85,6 +85,7 @@ export type IconButtonProps = ButtonA11yProps & {
unsafeDisableTooltip?: boolean
description?: string
tooltipDirection?: TooltipDirection
+ keyshortcuts?: string
} & Omit
// adopted from React.AnchorHTMLAttributes
diff --git a/script/generate-e2e-tests.js b/script/generate-e2e-tests.js
index 555b9157766..4c8d1524b2a 100644
--- a/script/generate-e2e-tests.js
+++ b/script/generate-e2e-tests.js
@@ -624,6 +624,14 @@ const components = new Map([
id: 'components-iconbutton-features--small',
name: 'Small',
},
+ {
+ id: 'components-iconbutton-features--keyshortcuts',
+ name: 'Keyshortcuts',
+ },
+ {
+ id: 'components-iconbutton-features--keyshortcuts-on-description',
+ name: 'Keyshortcuts on Description',
+ },
],
},
],