diff --git a/packages/mui-material/src/ButtonBase/ButtonBase.js b/packages/mui-material/src/ButtonBase/ButtonBase.js
index bf5f9c0c3a9a91..39fd531b63daf5 100644
--- a/packages/mui-material/src/ButtonBase/ButtonBase.js
+++ b/packages/mui-material/src/ButtonBase/ButtonBase.js
@@ -192,7 +192,16 @@ const ButtonBase = React.forwardRef(function ButtonBase(inProps, ref) {
const isNonNativeButton = () => {
const button = buttonRef.current;
- return component && component !== 'button' && !(button.tagName === 'A' && button.href);
+
+ if (!button) {
+ return component && component !== 'button';
+ }
+
+ if (button.tagName === 'BUTTON') {
+ return false;
+ }
+
+ return !(button.tagName === 'A' && button.href);
};
const handleKeyDown = useEventCallback((event) => {
@@ -240,7 +249,8 @@ const ButtonBase = React.forwardRef(function ButtonBase(inProps, ref) {
event.target === event.currentTarget &&
isNonNativeButton() &&
event.key === ' ' &&
- !event.defaultPrevented
+ !event.defaultPrevented &&
+ !disabled
) {
event.currentTarget.click();
}
diff --git a/packages/mui-material/src/ButtonBase/ButtonBase.test.js b/packages/mui-material/src/ButtonBase/ButtonBase.test.js
index 020dc9a3106815..c4e6322afcd57e 100644
--- a/packages/mui-material/src/ButtonBase/ButtonBase.test.js
+++ b/packages/mui-material/src/ButtonBase/ButtonBase.test.js
@@ -824,6 +824,32 @@ describe('', () => {
setProps({ disabled: false });
expect(button).not.to.have.attribute('aria-disabled');
});
+
+ it('should not propagate click events when Space is released on a disabled non-native button', async () => {
+ const parentClickSpy = spy();
+ const buttonClickSpy = spy();
+
+ const { user } = render(
+
+
+ Hello
+
+
,
+ );
+
+ const button = screen.getByRole('button');
+
+ // We don't use `user.tab()` because normal tab focus won't land on a disabled
+ // ButtonBase, only programmatic focus can happen
+ await act(async () => {
+ button.focus();
+ });
+
+ await user.keyboard(' ');
+
+ expect(buttonClickSpy.callCount).to.equal(0);
+ expect(parentClickSpy.callCount).to.equal(0);
+ });
});
describe('prop: component', () => {
@@ -1182,6 +1208,37 @@ describe('', () => {
expect(onClickSpy.callCount).to.equal(0);
});
+ it('should preserve native button keyboard behavior when a custom component renders a native button', async () => {
+ const onClickSpy = spy();
+ const onKeyDownSpy = spy();
+
+ /** @type {React.ForwardRefExoticComponent>} */
+ const MyButton = React.forwardRef((props, ref) => );
+
+ const { user } = render(
+
+ Hello
+ ,
+ );
+
+ await user.tab();
+
+ await user.keyboard('{Enter}');
+
+ expect(onKeyDownSpy.callCount).to.equal(1);
+ expect(onClickSpy.callCount).to.equal(1);
+ expect(onKeyDownSpy.firstCall.args[0]).to.have.property('defaultPrevented', false);
+
+ onClickSpy.resetHistory();
+ onKeyDownSpy.resetHistory();
+
+ await user.keyboard(' ');
+
+ expect(onKeyDownSpy.callCount).to.equal(1);
+ expect(onClickSpy.callCount).to.equal(1);
+ expect(onKeyDownSpy.firstCall.args[0]).to.have.property('defaultPrevented', false);
+ });
+
it('prevents default on Enter with an anchor and empty href', async () => {
const onClickSpy = spy();
const onKeyDownSpy = spy();