diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx index 8458ecd8ad5..667618780d3 100644 --- a/packages/react/src/Button/ButtonBase.tsx +++ b/packages/react/src/Button/ButtonBase.tsx @@ -60,9 +60,14 @@ const ButtonBase = forwardRef( const sxStyles = useMemo(() => { return merge(baseStyles, sxProp) }, [baseStyles, sxProp]) + const uuid = useId(id) const loadingAnnouncementID = `${uuid}-loading-announcement` - const buttonLabelID = ariaLabelledBy || `${uuid}-label` + + // generate a fallback aria-labelledby to point to text content, + // but only if the instance does not have an aria-label + const textContentId = `${uuid}-label` + const fallbackAriaLabelledById = props['aria-label'] ? undefined : textContentId if (__DEV__) { /** @@ -101,7 +106,7 @@ const ButtonBase = forwardRef( .filter(descriptionID => Boolean(descriptionID)) .join(' ')} // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state - aria-labelledby={buttonLabelID} + aria-labelledby={ariaLabelledBy || fallbackAriaLabelledById} id={id} onClick={loading ? undefined : onClick} > @@ -117,7 +122,7 @@ const ButtonBase = forwardRef( {loading && !LeadingVisual && !TrailingVisual && renderVisual(Spinner, loading, 'loadingSpinner')} {LeadingVisual && renderVisual(LeadingVisual, loading, 'leadingVisual')} {children && ( - + {children} {count !== undefined && !TrailingVisual && ( diff --git a/packages/react/src/Button/__tests__/Button.test.tsx b/packages/react/src/Button/__tests__/Button.test.tsx index 97a29b04c90..3c98265b831 100644 --- a/packages/react/src/Button/__tests__/Button.test.tsx +++ b/packages/react/src/Button/__tests__/Button.test.tsx @@ -249,4 +249,27 @@ describe('Button', () => { expect(container.getByRole('button')).toHaveAccessibleName('content') }) + + it('should add be labelled by only text content', () => { + const container = render() + + expect(container.getByRole('button')).toHaveAccessibleName('content') + }) + + it('should prefer aria-label over text content', () => { + const container = render() + + expect(container.getByRole('button')).toHaveAccessibleName('Custom label') + }) + + it('should prefer aria-labelledby over text content', () => { + const container = render( + <> + different label + + , + ) + + expect(container.getByRole('button')).toHaveAccessibleName('different label') + }) })