diff --git a/packages/mui-system/src/cssVars/cssVarsParser.test.ts b/packages/mui-system/src/cssVars/cssVarsParser.test.ts index f8b4e58d015b32..291b985fe375ba 100644 --- a/packages/mui-system/src/cssVars/cssVarsParser.test.ts +++ b/packages/mui-system/src/cssVars/cssVarsParser.test.ts @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { vi } from 'vitest'; import cssVarsParser, { assignNestedKeys, walkObjectDeep } from './cssVarsParser'; describe('cssVarsParser', () => { @@ -405,4 +406,48 @@ describe('cssVarsParser', () => { expect(css).to.deep.equal({}); expect(vars).to.deep.equal({}); }); + + it('skips string values containing "//" to avoid stylis comment corruption', () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const { css, vars } = cssVarsParser({ + palette: { + primary: { + main: '#000', + }, + }, + backgroundImage: 'url(https://example.com/image.png)', + }); + expect(css).to.not.have.property('--backgroundImage'); + expect(vars).to.not.have.property('backgroundImage'); + expect(css).to.deep.equal({ '--palette-primary-main': '#000' }); + expect(errorSpy.mock.calls[0][0]).to.include('backgroundImage'); + expect(errorSpy.mock.calls[0][0]).to.include('//'); + errorSpy.mockRestore(); + }); + + it('skips only the URL value and preserves other variables', () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const { css, vars } = cssVarsParser({ + palette: { + primary: { + main: '#ff5252', + image: 'url(https://example.com/image.png)', + dark: '#b71c1c', + }, + }, + }); + expect(css).to.deep.equal({ + '--palette-primary-main': '#ff5252', + '--palette-primary-dark': '#b71c1c', + }); + expect(vars).to.deep.equal({ + palette: { + primary: { + main: 'var(--palette-primary-main)', + dark: 'var(--palette-primary-dark)', + }, + }, + }); + errorSpy.mockRestore(); + }); }); diff --git a/packages/mui-system/src/cssVars/cssVarsParser.ts b/packages/mui-system/src/cssVars/cssVarsParser.ts index c1876b18f1abc1..373faaa2113fce 100644 --- a/packages/mui-system/src/cssVars/cssVarsParser.ts +++ b/packages/mui-system/src/cssVars/cssVarsParser.ts @@ -137,6 +137,16 @@ export default function cssVarsParser>( theme, (keys, value: string | number | object, arrayKeys) => { if (typeof value === 'string' || typeof value === 'number') { + if (typeof value === 'string' && value.includes('//')) { + // Stylis (the CSS preprocessor used by Emotion) treats `//` as a single-line comment, + // which corrupts the entire :root {} block when a URL value is serialized as a CSS variable. + if (process.env.NODE_ENV !== 'production') { + console.error( + `MUI: The theme key "${keys.join('.')}" value ("${value}") contains "//" which is treated as a comment by the CSS preprocessor and cannot be used as a CSS variable value. Use \`shouldSkipGeneratingVar\` to exclude this key from CSS variable generation.`, + ); + } + return; + } if (!shouldSkipGeneratingVar || !shouldSkipGeneratingVar(keys, value)) { // only create css & var if `shouldSkipGeneratingVar` return false const cssVar = `--${prefix ? `${prefix}-` : ''}${keys.join('-')}`; diff --git a/skills/material-ui-theming/AGENTS.md b/skills/material-ui-theming/AGENTS.md index 7888d56a31d3af..a6757e12a8d814 100644 --- a/skills/material-ui-theming/AGENTS.md +++ b/skills/material-ui-theming/AGENTS.md @@ -81,6 +81,7 @@ Full defaults: [Default theme explorer](https://mui.com/material-ui/customizatio - `InitColorSchemeScript`: place before any rendered content to prevent the initial color-scheme flash. App Router: inside `` before `{children}` in `app/layout.tsx`. Pages Router: in `_document.tsx` before `
`. See [Preventing SSR flickering](https://mui.com/material-ui/customization/css-theme-variables/configuration.md#preventing-ssr-flickering). - Trade-offs: larger HTML (both schemes' variables), possible FCP impact; benefits include less JavaScript work on scheme switch and better SSR dark experience. See [Overview](https://mui.com/material-ui/customization/css-theme-variables/overview.md). - `CssVarsProvider` is superseded by `ThemeProvider` with the same capabilities. Use `ThemeProvider`. +- **URL values and `//` in theme strings**: Stylis (the CSS preprocessor used by Emotion) treats `//` as a single-line comment. Any theme string value containing `//` — such as a `backgroundImage` URL like `https://…` — will corrupt the generated `:root {}` block and make all subsequent `--mui-*` CSS variables undefined. Use `shouldSkipGeneratingVar` to exclude these keys from CSS variable generation: `createTheme({ cssVariables: { shouldSkipGeneratingVar: (keys) => keys.includes('backgroundImage') } })`. A dev-mode `console.error` is emitted if such a value is detected. ---