diff --git a/.changeset/ripe-baths-rush.md b/.changeset/ripe-baths-rush.md
new file mode 100644
index 00000000000..b4274f32094
--- /dev/null
+++ b/.changeset/ripe-baths-rush.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': patch
+---
+
+Only shows the aria-describedby id for loading when the component is in the loading state
diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx
index ba376b52eae..d2ae5a0b6ba 100644
--- a/packages/react/src/Button/ButtonBase.tsx
+++ b/packages/react/src/Button/ButtonBase.tsx
@@ -56,6 +56,9 @@ const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, f
const uuid = useId(id)
const loadingAnnouncementID = `${uuid}-loading-announcement`
+ // Only include the loading aria-describedby if there is a loading state
+ const ariaDescribedByIds = loading ? [loadingAnnouncementID, ariaDescribedBy] : [ariaDescribedBy]
+
if (__DEV__) {
/**
* The Linter yells because it thinks this conditionally calls an effect,
@@ -100,9 +103,7 @@ const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, f
data-variant={variant}
data-label-wrap={labelWrap}
data-has-count={count !== undefined ? true : undefined}
- aria-describedby={[loadingAnnouncementID, ariaDescribedBy]
- .filter(descriptionID => Boolean(descriptionID))
- .join(' ')}
+ aria-describedby={ariaDescribedByIds.filter(descriptionID => Boolean(descriptionID)).join(' ') || undefined}
// aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state.
// We only set it when the button is in a loading state because it will supersede the aria-label when the screen
// reader announces the button name.
diff --git a/packages/react/src/Button/__tests__/Button.test.tsx b/packages/react/src/Button/__tests__/Button.test.tsx
index 5251ed5491d..4f42fbb1b15 100644
--- a/packages/react/src/Button/__tests__/Button.test.tsx
+++ b/packages/react/src/Button/__tests__/Button.test.tsx
@@ -170,6 +170,8 @@ describe('Button', () => {
)
const buttonNode = container.getByRole('button')
+ fireEvent.click(buttonNode)
+
expect(buttonNode.getAttribute('aria-describedby')).toBe(`${buttonId}-loading-announcement`)
fireEvent.click(buttonNode)
@@ -192,6 +194,8 @@ describe('Button', () => {
)
const buttonNode = container.getByRole('button')
+ fireEvent.click(buttonNode)
+
expect(buttonNode.getAttribute('aria-describedby')).toBe(`${buttonId}-loading-announcement`)
fireEvent.click(buttonNode)
@@ -213,6 +217,10 @@ describe('Button', () => {
content
,
)
+ const buttonNode = container.getByRole('button')
+
+ fireEvent.click(buttonNode)
+
const buttonDescribedBy = container.getByRole('button').getAttribute('aria-describedby')
const loadingAnnouncementId = `${buttonId}-loading-announcement`
diff --git a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap
index 0ddfa63424b..0e24734daa4 100644
--- a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap
+++ b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap
@@ -2,7 +2,6 @@
exports[`Button > respects block prop 1`] = `