mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
94.02k stars 32.3k forks source link

SX and Styled can't override applyStyles css #44488

Open RickyRoller opened 1 day ago

RickyRoller commented 1 day ago

Steps to reproduce

Repro link

Steps:

  1. Setup theme to use css variables
  2. Use theme.applyStyles to apply dark theme styles to component
  3. Sx and Styled components can't overwrite the dark theme styles

Current behavior

Sx and Styled theme behavior breaks with this new paradigm. They can't override the styles if the base component is using the new applyStyles helper since it creates a css selector with higher specificity

Expected behavior

Sx and Styled should be able to override styles like they used to with theme.palette.mode conditional logic

Context

There are multiple issues with the new css variables system.

  1. Sx and Styled can't override styles that use theme.applyStyles due to specificty
  2. theme.applyStyles doesn't support template literal syntax so the css selector has to either be extracted to a var or hardcoded
  3. Docs are inconsistent on recommendations. Docs say to use spread syntax on theme.applyStyles, then the actual code files say to not use spread syntax. ie. Docs, Code
  4. applyStyles isn't a valid replacement for controlling the theme of specific components. If you only specify dark theme styles with applyStyles, then adding .mode-light to a component won't apply those styles since .mode-dark has higher specificity. Refer to link for example

The biggest issue is the breaking change with sx/styled behavior

/**
 * All 3 buttons should be green (#bada55)
 */

/**
 * Light theme should be red
 * Dark theme should be blue
 */

const BaseButton = styled(Button)(({ theme }) => ({
  background: 'red',
  color: '#fff',
  ...theme.applyStyles('dark', {
    background: 'steelblue',
    color: '#fff',
  }),
}));

/**
 * Styled can't override dark theme either
 */

const StyledButton = styled(BaseButton)`
  background: #bada55;
`;

/**
 * No support for applyStyles with template literals
 */

const TemplateButton = styled(Button)`
  background: red;
  color: #fff;
  *:where(.mode-dark) & {
    background: aliceblue;
    color: #555;
  }
`;

/**
 * applyStyles isn't a valid alternative to controlling theme for a specific component.
 * If you don't split all styles into applyStyles('light') and applyStyles('dark') then it won't apply the alternative
 */

const Themed = () => (
  <div className=".mode-light">
    <BaseButton variant="contained">Light Themed</BaseButton>
  </div>
);

export default function BasicButtons() {
  return (
    <Stack spacing={2} direction="row">
      <BaseButton
        variant="contained"
        sx={{
          background: '#bada55',
        }}
      >
        Dark
      </BaseButton>
      <StyledButton variant="contained">Styled</StyledButton>
      <TemplateButton
        variant="contained"
        sx={{
          background: '#bada55',
        }}
      >
        Template
      </TemplateButton>
      <Themed />
    </Stack>
  );
}

Screenshot 2024-11-20 at 11 44 00 AM

Your environment

npx @mui/envinfo ``` System: OS: macOS 14.1.1 Binaries: Node: 20.12.2 - ~/.nvm/versions/node/v20.12.2/bin/node npm: 10.8.3 - ~/.nvm/versions/node/v20.12.2/bin/npm pnpm: 9.13.0 - ~/.nvm/versions/node/v20.12.2/bin/pnpm Browsers: Chrome: 131.0.6778.71 Edge: Not Found Safari: 17.1 ```

Search keywords: css variables, css vars, applyStyles, sx, styled

siriwatknp commented 1 hour ago

Sx and Styled theme behavior breaks with this new paradigm. They can't override the styles if the base component is using the new applyStyles helper since it creates a css selector with higher specificity

This is expected by design. When CSS variables is enable, the applyStyles create a new stylesheet for overriding styles. The result correspond to the code:

const BaseButton = styled(Button)(({ theme }) => ({
  background: 'red',
  color: '#fff',
  ...theme.applyStyles('dark', {
    background: 'steelblue',
    color: '#fff',
  }),
}));

// css
.button-hash {
  background: 'red',
  color: '#fff',
}
:where(.mode-dark) .button-hash {
  background: 'steelblue',
  color: '#fff',
}

Based on your code, if you want to use .mode-light to change the styles, you must change the component implementation to use CSS variables as values instead of raw values.

const BaseButton = styled(Button)(({ theme }) => ({
  background: theme.vars.palette.*,
  color: theme.vars.palette.*,
}));