Skip to content

Refactor: Migrate MUI theme to CssVarsProvider with light + dark colorSchemes #7281

@MarkusNeusinger

Description

@MarkusNeusinger

Problem

The MUI theme in app/src/main.tsx is hard-pinned to palette.mode: 'light'. Dark mode is implemented entirely via CSS custom properties (--bg-page, --ink, --ink-soft, --rule, …) toggled by [data-theme="dark"] on <html> (see app/src/styles/tokens.css and app/src/hooks/useThemeMode.ts).

This works for hand-built components that opt in to the CSS tokens, but every MUI surface is dark-mode-blind: Paper, Popover, Dialog, TextField (placeholder/input/outline), Button hover overlays, Divider, focus rings, action.hover, action.selected, etc. all remain on light-palette values regardless of theme.

The consequence: every component using a MUI surface has to manually patch bgcolor / color / borderColor with var(--bg-*) / var(--ink*) in its sx prop — and inputs need extra & .MuiOutlinedInput-input::placeholder { color: ... } selectors. Easy to forget, inconsistent, and a recurring source of bugs.

Example bug: FeedbackWidget — the FAB, mini-FAB stack, "Thanks!" toast, and the entire popover (Paper + TextFields + secondary text) appeared as light surfaces on the dark page. Fixed locally in #TBD, but the underlying issue is the theme architecture, not the widget.

Proposed solution

Migrate to MUI's CssVarsProvider with both colorSchemes:

const theme = extendTheme({
  colorSchemes: {
    light: { palette: { /* current light palette */ } },
    dark:  { palette: { /* dark equivalents */ } },
  },
  // …
});

Map the existing CSS tokens (--bg-page, --bg-surface, --bg-elevated, --ink, --ink-soft, --ink-muted, --rule) into MUI palette tokens so:

  • background.default--bg-page
  • background.paper--bg-elevated
  • text.primary--ink
  • text.secondary--ink-soft
  • divider--rule
  • action.hover etc. — tuned per scheme

Switch useThemeMode to toggle MUI's data-mui-color-scheme attribute (or reuse data-theme via attribute config) so MUI and the existing CSS tokens stay in sync from a single signal.

Wins

  • MUI components adapt to dark mode automatically — no per-component sx patches
  • Single source of truth for both schemes
  • Keeps CSS-variables performance (MUI v6+ emits CSS vars internally)
  • Removes the recurring "I forgot to dark-mode this surface" bug class

Scope / risk

  • Touches every component that currently overrides bgcolor / color with var(--bg-*) (can usually be deleted, not replaced)
  • Visual regression risk on every surface — needs a screenshot pass in both modes
  • Should be its own PR; no need to bundle with feature work

Out of scope

  • The local fix in #TBD addresses the FeedbackWidget symptom. This issue is the structural cure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestinfrastructureWorkflow, backend, or frontend issue

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions