Skip to Content
GuidesMaterial UI (MUI)

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, 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) — opens RatingDialog, 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.

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 &ldquo;{payload.itemName}&rdquo;</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' }, });
Last updated on