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.
Problem
The MUI theme in
app/src/main.tsxis hard-pinned topalette.mode: 'light'. Dark mode is implemented entirely via CSS custom properties (--bg-page,--ink,--ink-soft,--rule, …) toggled by[data-theme="dark"]on<html>(seeapp/src/styles/tokens.cssandapp/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),Buttonhover 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/borderColorwithvar(--bg-*)/var(--ink*)in itssxprop — 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
CssVarsProviderwith bothcolorSchemes: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-pagebackground.paper←--bg-elevatedtext.primary←--inktext.secondary←--ink-softdivider←--ruleaction.hoveretc. — tuned per schemeSwitch
useThemeModeto toggle MUI'sdata-mui-color-schemeattribute (or reusedata-themeviaattributeconfig) so MUI and the existing CSS tokens stay in sync from a single signal.Wins
sxpatchesScope / risk
bgcolor/colorwithvar(--bg-*)(can usually be deleted, not replaced)Out of scope