Material UI (MUI)
Wire @lamstack/react-dialog’s templates to MUI’s own Dialog/DialogTitle/DialogContent/
DialogActions components — DialogsProvider and useDialogs() don’t know or care that
MUI is involved.
Live demo
Try all four ways to open a dialog — the log below shows exactly what each call resolved with:
alert()— acknowledgement only, resolvesvoidonce dismissed.confirm()— resolvestrue/falsedepending on which button was pressed.prompt()— resolves the typed string, ornullif cancelled.open(custom)— opensRatingDialog, built with MUI’s own<Rating>component and a payload/result type ({ itemName: string }→number | null) that has nothing to do with the three built-in kinds above.
Preview
alert()
Acknowledgement only — resolves void once dismissed.
confirm()
Resolves true/false depending on which button was pressed.
prompt()
Resolves the typed string, or null if cancelled.
open(custom)
Any component works, not just alert/confirm/prompt — here, a rating.
Chaining dialogs with async/await
Each call blocks until its dialog closes, so a multi-step flow — confirm, then prompt, then alert — reads top-to-bottom. No nested callbacks, no extra state for tracking which dialog is currently open.
Loading state via async onClose()
onClose runs before the promise resolves — the dialog awaits it itself to show “Saving…” with no extra state passed in from here.
Note MUI’s <Dialog onClose> prop has a different signature than
@lamstack/react-dialog’s DialogProps.onClose ((event, reason) => void vs. (result: R) => Promise<void>) — map it explicitly (onClose={() => onClose(false)}) rather than passing
onClose straight through, so backdrop-click/Escape resolve with a sensible result too.
A custom (non-template) dialog
The open(custom) button above uses MUI’s <Rating> component inside a dialog with its
own payload/result types — nothing about open() restricts you to the three built-in
kinds:
import * as React from 'react';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Rating from '@mui/material/Rating';
import Button from '@mui/material/Button';
import { useDialogs } from '@lamstack/react-dialog';
import type { DialogProps } from '@lamstack/react-dialog';
interface ItemPayload {
itemName: string;
}
function RatingDialog({ payload, open, onClose }: DialogProps<ItemPayload, number | null>) {
const [value, setValue] = React.useState<number | null>(null);
return (
<Dialog open={open} onClose={() => onClose(null)}>
<DialogTitle>Rate “{payload.itemName}”</DialogTitle>
<DialogContent>
<Rating value={value} onChange={(_event, newValue) => setValue(newValue)} />
</DialogContent>
<DialogActions>
<Button onClick={() => onClose(null)}>Skip</Button>
<Button disabled={value === null} onClick={() => onClose(value)} variant="contained">
Submit
</Button>
</DialogActions>
</Dialog>
);
}
function RateButton() {
const { open } = useDialogs();
return (
<Button
onClick={async () => console.log(await open(RatingDialog, { itemName: 'Acme Widget' }))}
>
Rate this
</Button>
);
}Next.js App Router setup
MUI’s styles are generated by Emotion at render time; without a shared cache, Next.js’s App
Router re-generates a <style> tag per component during SSR. Wrap the whole app once in
the root layout with @mui/material-nextjs’s cache provider (pick the entry matching your
Next.js major version, e.g. v16-appRouter for Next 16):
// app/layout.tsx
import { AppRouterCacheProvider } from '@mui/material-nextjs/v16-appRouter';
import { ThemeProvider } from '@mui/material/styles';
import { muiTheme } from '../lib/mui-theme';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
<ThemeProvider theme={muiTheme}>{children}</ThemeProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}enableCssLayer: true wraps MUI’s generated styles in @layer mui, which keeps them from
winning specificity fights against plain CSS or Tailwind utility classes used elsewhere in
the same app (this docs site also has a Tailwind CSS guide — the two
coexist without enableCssLayer fighting over button styles).
Dark mode
This demo’s theme uses MUI’s CSS-variables-based color schemes with
colorSchemeSelector: 'class', so it reads whichever dark/light class is already on
<html> — the same class this docs site’s own theme switcher toggles — instead of
introducing a second, separate dark-mode toggle:
// lib/mui-theme.ts
import { createTheme } from '@mui/material/styles';
export const muiTheme = createTheme({
colorSchemes: { light: true, dark: true },
cssVariables: { colorSchemeSelector: 'class' },
});