Question
Does anyone know a simple way to change the order in which Dialog and Alert elements are closed when the user presses the Escape key? MUI seems to prefer closing Dialogs then Alerts, regardless of the order in which they are opened or their z-index.
Problem Statement
I am working on a large React application that uses both MUI Alert and Dialog components in nearly 50+ files. It is a common scenario for a user to have Alerts and Dialogs open simultaneously, and Alerts have a higher z-index than Dialogs.
When both of these components are open (Alert over Dialog) and the user presses the Escape key once, the Dialog is closed first, then the Alert on the second key press.
Expected Behavior
In a single-alert, single-dialog scenario, it was expected the Alert would close first, then the Dialog, since the Alert appeared on top.
Desired Solution
I’m looking for a potential MUI configuration or theme change that would accomplish the desired result (if known or documented in the docs for Material UI). The desired solution would not involve the use of hand-written Dialog components that depend on a custom alert state.
Instructions to Reproduce
I included an example repo to easily test these changes on a local environment:
-
Clone or download the example GitHub repository.
https://github.com/g1hanna/mui-alert-dialog-example -
Run
npm i --legacy-peer-deps
to download the repo’s dependencies. -
Run
npm run build
andnpm run start
to view the example in a browser.
(Latest version of Chrome recommended, but Firefox and Edge work too.) -
Once the example is running, click on “Open Modal” to open a MUI Dialog.
-
Click on “Open Toast” to open a MUI Snackbar.
-
Press the Escape key. The Dialog will close before the Snackbar.
Demo
Example on Code Sandbox
Source Code
src/App.tsx
import React, { useState } from 'react';
import { Stack, Button, Dialog, DialogTitle, Typography, DialogContent, DialogActions, Snackbar, Box, Alert, AlertTitle } from '@mui/material';
import './App.css';
function App() {
const [snackbarOpen, setSnackbarOpen] = useState<boolean>(false);
const [modalOpen, setModalOpen] = useState<boolean>(false);
return (
<div className="App">
<Stack direction="row">
<Button onClick={() => setModalOpen(true)}>Open Modal</Button>
</Stack>
<Dialog
open={modalOpen}
onClose={(_, reason) => {
if (reason !== "escapeKeyDown") return;
setModalOpen(false);
}}
>
<DialogTitle>
<Typography variant="h4" noWrap>
Dialog Header
</Typography>
</DialogTitle>
<DialogContent
dividers
sx={{
display: "flex",
flexDirection: "column",
overflowY: "auto",
}}
>
<Typography>Press "Escape" to close me</Typography>
</DialogContent>
<DialogActions data-testid="modal-buttons-box" sx={{ p: 2 }}>
<Button onClick={() => setSnackbarOpen(true)}>Open Toast</Button>
</DialogActions>
</Dialog>
<Snackbar
open={snackbarOpen}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
onClose={(_, reason) => {
if (reason !== "escapeKeyDown") return;
setSnackbarOpen(false);
}}
>
<Box>
<Alert>
<AlertTitle sx={{ textAlign: "left" }}>Toast Title</AlertTitle>
Press "Escape" to close me
</Alert>
</Box>
</Snackbar>
</div>
);
}
export default App;
src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App.tsx';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
package.json
{
"name": "package-name",
"version": "0.1.0",
"type": "module",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.2.1",
"@mui/material": "^6.2.1",
"@mui/system": "^6.2.1",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.2",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"cra-template-typescript": "1.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-scripts": "5.0.1",
"typescript": "^5.7.2",
"web-vitals": "^4.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2"
}
}
tsconfig.json
{
"compilerOptions": {
"module": "NodeNext",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true,
"jsx": "react",
"allowSyntheticDefaultImports": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"moduleResolution": "nodenext",
"typeRoots" : ["node_modules/@types", "src/types"],
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/delcarations.d.ts"],
}
hannag1 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.