MUI

Overview

MUI (Material UI) is a React component library that implements Google's Material Design. It ships with a comprehensive set of pre-built components and a powerful theming system that accepts a fully typed theme object — covering colors, typography, spacing, shape, breakpoints, and component-level overrides — through its createTheme API.

Project Files

FilePurpose
muiTheme.tscreateTheme() with full light and dark palettes
MuiThemeProvider.tsxApplies the MUI theme via ThemeProvider
MuiThemeSyncer.tsxKeeps MUI's color scheme in sync with next-themes for light / dark mode

Configuration

Theme

MUI reads from tokens.ts. Both light and dark palettes are fully populated using the colorSchemes API introduced in MUI v7:

import { tokens } from "@/styles/_generated/tokens";

export const muiTheme = createTheme({
  colorSchemes: {
    light: {
      palette: {
        primary:    { main: tokens.light.colorPrimary },
        secondary:  { main: tokens.light.colorSecondary },
        background: { default: tokens.light.colorBackground },
      },
    },
    dark: {
      palette: {
        primary:    { main: tokens.dark.colorPrimary },
        secondary:  { main: tokens.dark.colorSecondary },
        background: { default: tokens.dark.colorBackground },
      },
    },
  },
});

Overrides

muiComponentOverrides.ts applies default props globally to every MUI component instance via the components key in createTheme:

export const muiTheme = createTheme({
  components: {
    MuiButtonBase: {
      defaultProps: {
        LinkComponent: Link, // swaps MUI's default `<a>` with Next.js `<Link>` for client-side navigation
      },
    },
    MuiMenu: {
      defaultProps: {
        disableScrollLock: true, // prevents layout shift from the scrollbar being hidden
      },
    },
  },
});

Light & Dark Mode

dark Class

MUI's light and dark palette values are populated via this project's codegen pipeline.

colorSchemeSelector: "class" tells MUI to activate its dark palette when dark is on <html>, which is what next-themes toggles.

export const muiTheme = createTheme({
  colorSchemeSelector: "class"
});

Syncers

While the dark class is enough for light / dark mode to work visually, MUI keeps its own internal state of which color scheme it is in.

When next-themes toggles the dark class, MUI does not detect this.

For any extra MUI implementation & client-side functionality that needs to pull data from MUI's state, a Syncer is needed to keep it up to date with the resolved theme from next-themes.

MuiThemeSyncer calls setMode() whenever resolvedTheme changes to keep MUI's internal state in sync.

export const MuiThemeSyncer = () => {
  const { resolvedTheme } = useTheme();
  const { setMode } = useColorScheme();

  useEffect(() => {
    setMode(resolvedTheme === "dark" ? "dark" : "light");
  }, [resolvedTheme, setMode]);

  return null;
};

This Syncer is placed inside the MuiThemeProvider so all components have access to useColorScheme():

type MuiThemeProviderProps = {
  children: ReactNode;
};

export const MuiThemeProvider = ({ children }: MuiThemeProviderProps) => {
  return (
    <AppRouterCacheProvider options={{ enableCssLayer: true }}>
      <ThemeProvider theme={muiTheme}>
        <MuiThemeSyncer />
        {children}
      </ThemeProvider>
    </AppRouterCacheProvider>
  );
};

Injection Script

To avoid a flash on first load, MUI's InitMuiColorSchemeScript is placed in layout.tsx and run before serving the page.

// layout.tsx
<InitMuiColorSchemeScript attribute="class" defaultMode="system" />

See Pain Points: SSR Flicker for details.

Components

All MUI components work exactly as documented. The project theme is applied automatically via MuiThemeProvider, so every component inherits the correct colors, typography, and shape without any extra setup.

Official Resources