From 54cd641bf39180d4c9d7313d4810665f846c13ff Mon Sep 17 00:00:00 2001 From: Nadeem P Date: Thu, 11 Dec 2025 05:32:46 +0000 Subject: [PATCH 1/2] Add F6 keyboard shortcut for accessibility --- .../components/snackbars/snackbars.md | 4 ++ .../mui-material/src/Snackbar/Snackbar.js | 33 ++++++++++- .../src/Snackbar/Snackbar.test.js | 55 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/docs/data/material/components/snackbars/snackbars.md b/docs/data/material/components/snackbars/snackbars.md index 2bda8ee964613a..be4ae038f3d337 100644 --- a/docs/data/material/components/snackbars/snackbars.md +++ b/docs/data/material/components/snackbars/snackbars.md @@ -122,6 +122,10 @@ Note that notistack prevents Snackbars from being [closed by pressing F6. This allows keyboard users to access the Snackbar and its interactive elements without having to tab through the entire page. + The user should be able to dismiss Snackbars by pressing Escape. If there are multiple instances appearing at the same time and you want Escape to dismiss only the oldest one that's currently open, call `event.preventDefault` in the `onClose` prop. ```jsx diff --git a/packages/mui-material/src/Snackbar/Snackbar.js b/packages/mui-material/src/Snackbar/Snackbar.js index 4d015d43f665aa..c8832b5856f1d8 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.js +++ b/packages/mui-material/src/Snackbar/Snackbar.js @@ -139,8 +139,34 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { const { getRootProps, onClickAway } = useSnackbar(ownerState); + const snackbarRef = React.useRef(null); + const [exited, setExited] = React.useState(true); + React.useEffect(() => { + if (!open) { + return undefined; + } + + /** + * @param {KeyboardEvent} nativeEvent + */ + function handleKeyDown(nativeEvent) { + if (!nativeEvent.defaultPrevented) { + if (nativeEvent.key === 'F6' && snackbarRef.current) { + nativeEvent.preventDefault(); + snackbarRef.current.focus(); + } + } + } + + document.addEventListener('keydown', handleKeyDown); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [open]); + const handleExited = (node) => { setExited(true); @@ -174,7 +200,12 @@ const Snackbar = React.forwardRef(function Snackbar(inProps, ref) { ref, className: [classes.root, className], elementType: SnackbarRoot, - getSlotProps: getRootProps, + getSlotProps: (handlers) => ({ + ...getRootProps(handlers), + ref: snackbarRef, + tabIndex: 0, + role: 'status', + }), externalForwardedProps: { ...externalForwardedProps, ...other, diff --git a/packages/mui-material/src/Snackbar/Snackbar.test.js b/packages/mui-material/src/Snackbar/Snackbar.test.js index 0789ddadf86537..9a0b8367f98838 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.test.js +++ b/packages/mui-material/src/Snackbar/Snackbar.test.js @@ -635,4 +635,59 @@ describe('', () => { expect(handleClose.callCount).to.equal(0); }); + describe('F6 keyboard shortcut', () => { + it('should focus Snackbar when F6 is pressed', () => { + const { container } = render(); + + const snackbar = container.querySelector('[role="status"]'); + expect(snackbar).not.to.equal(null); + expect(snackbar).not.to.equal(document.activeElement); + + fireEvent.keyDown(document.body, { key: 'F6' }); + + expect(snackbar).to.equal(document.activeElement); + }); + + it('should not focus when Snackbar is closed', () => { + render(); + + const snackbar = document.querySelector('[role="status"]'); + expect(snackbar).to.equal(null); + + fireEvent.keyDown(document.body, { key: 'F6' }); + + expect(document.activeElement).to.equal(document.body); + }); + + it('should have role="status" for accessibility', () => { + const { container } = render(); + + const snackbar = container.querySelector('[role="status"]'); + expect(snackbar).not.to.equal(null); + }); + + it('should have tabIndex="0" to be focusable', () => { + const { container } = render(); + + const snackbar = container.querySelector('[role="status"]'); + expect(snackbar.getAttribute('tabindex')).to.equal('0'); + }); + + it('should work with multiple Snackbars', () => { + const { container } = render( + + + + , + ); + + fireEvent.keyDown(document.body, { key: 'F6' }); + + const snackbars = container.querySelectorAll('[role="status"]'); + expect(snackbars.length).to.equal(2); + // One of the Snackbars should be focused + const focusedSnackbar = Array.from(snackbars).find((s) => s === document.activeElement); + expect(focusedSnackbar).not.to.equal(undefined); + }); + }); }); From 1a66e606bab6ef06a53f2a83b2ddb869fe46bdd9 Mon Sep 17 00:00:00 2001 From: Nadeem P Date: Thu, 11 Dec 2025 05:42:53 +0000 Subject: [PATCH 2/2] removed comments from test --- packages/mui-material/src/Snackbar/Snackbar.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui-material/src/Snackbar/Snackbar.test.js b/packages/mui-material/src/Snackbar/Snackbar.test.js index 9a0b8367f98838..0fb0458eef4f19 100644 --- a/packages/mui-material/src/Snackbar/Snackbar.test.js +++ b/packages/mui-material/src/Snackbar/Snackbar.test.js @@ -685,7 +685,7 @@ describe('', () => { const snackbars = container.querySelectorAll('[role="status"]'); expect(snackbars.length).to.equal(2); - // One of the Snackbars should be focused + const focusedSnackbar = Array.from(snackbars).find((s) => s === document.activeElement); expect(focusedSnackbar).not.to.equal(undefined); });