Skip to content

Valid CSS color strings can throw during render when Link has underline="always", crashes the app #49052

Description

@svitovyda

Steps to reproduce

Steps:

  1. Open this link to the live example;
  2. See that there is a Link rendered with a valid CSS color keyword and underline="always";
  3. See that theme.default.ts contains a MuiTypography variant which defines the CSS color for color="white";
  4. The Link is wrapped in an ErrorBoundary so the render failure is visible in the example;
  5. Observe in the console that rendering the Link throws MUI: Unsupported "white" color. This is an unhandled exception that breaks the entire app;

Current behavior

  1. MUI does not handle the runtime exception caused by its internal underline-color calculation. The exception escapes from Link during render and can therefore propagate to the application's ErrorBoundary. If the boundary is at the application level, a color used for a link can effectively take down the entire application UI.

  2. A valid color value that is already handled by the component's themed styling should not cause Link to fail because a separate internal implementation of the underline does not know how to process that same color.

  3. In this case, the color is already explicitly resolved by the MuiTypography theme configuration. Link should either use the resulting/resolved color for its underline styling, or otherwise avoid independently interpreting the original semantic color prop using a more restrictive color parser.

  4. There also appears to be no supported way to opt out of this additional underline-color processing while continuing to use MUI's built-in underline variants.

For example, I would expect to be able to define the three standard states in the normal MUI way:

underlineAlways: ({ theme }) => ({
  ...{some style, expecting textDecoration: 'underline' is already applied out of the box}
}),
underlineHover: ({ theme }) => ({
  ...{some style, expecting textDecoration: 'underline' is already applied in the hover selector out of the box}
}),
underlineNone: ({ theme }) => ({
  ...{some style, expecting textDecoration: 'none' is already applied out of the box}
}),

However, underline="always" performs additional internal color processing, which is not performed in the same way by the other underline states and which cannot be controlled through these style overrides.

The only way to override the color used by that implementation is to know about and explicitly set the internal CSS variable:

'--Link-underlineColor': theme.palette.main.white,

This means that customizing what appears to be a straightforward text-decoration state unexpectedly requires knowledge of an internal underline-color mechanism and its CSS variable.

More importantly, there does not appear to be an option equivalent to something such as disableRipple that disables MUI's additional underline customization while preserving the underline API and its state semantics.

As a result, to avoid this behavior entirely, I have to stop using MUI's built-in underline states:

defaultProps: {
  underline: 'none',
},

and define three separate variants:

declare module '@mui/material/Link' {
  interface LinkPropsVariantOverrides {
    underline_always: true;
    underline_hover: true;
    underline_never: true;
  }

I then have to reimplement textDecoration: 'underline', its removal, and the hover behavior for those variants myself.

This seems unnecessarily difficult to opt out of for what is ultimately underline presentation. The built-in underline API couples basic text-decoration behavior to an additional color-transformation mechanism which (a) can throw during render for otherwise valid/themed colors, (b) is not controlled by the corresponding style overrides, and (c) cannot be disabled independently without abandoning the built-in underline states and recreating them.

Expected behavior

  1. MUI should not allow an internal styling calculation to cause the entire application subtree to crash. Calls such as alpha(color, 0.4) should only be made after their inputs have been validated, or the failure should otherwise be handled gracefully.
  2. A valid color value which is accepted and handled by the component's themed styling should not cause Link to throw while calculating its underline style. Either the accepted color type should reflect the actual restrictions of the internal color processing, or that processing should support valid CSS color values such as named colors.
  3. Theme-level component customization should be respected consistently. In this case, MuiTypography explicitly defines how color="white" is styled, but Link independently processes the original unresolved prop value when calculating the underline color. The underline implementation should not bypass or conflict with the color styling already defined through the theme.
  4. There should be a simple supported way to disable MUI's additional underline-color processing while retaining the built-in underline states and their basic text-decoration behavior. Customizing Link underline styles should not require relying on an internal CSS variable or completely reimplementing the always, hover, and none behaviors as custom variants.

Context

We encountered this in an application after changing our Link theme override so that links use underline="always".

Existing usages with color="white" had previously rendered normally. Once the underline became always, the application started to crash, seemingly at random.

And our theme already defines the intended styling for Typography with color="white", so we didn't expect any issues with it at all.

Your environment

  1. On StackBlitz:
System:
    OS: Linux 5.0 undefined
  Binaries:
    Node: 22.22.3 - /usr/local/bin/node
    npm: 10.8.2 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/bin/pnpm
  Browsers:
    Chrome: Not Found
    Firefox: Not Found
  npmPackages:
    @emotion/react: latest => 11.14.0 
    @emotion/styled: latest => 11.14.1 
    @mui/core-downloads-tracker:  9.4.0 
    @mui/icons-material: latest => 9.4.0 
    @mui/material: latest => 9.4.0 
    @mui/private-theming:  9.4.0 
    @mui/styled-engine:  9.4.0 
    @mui/system:  9.4.0 
    @mui/types:  9.4.0 
    @mui/utils:  9.4.0 
    @types/react: latest => 19.2.18 
    @webassembly/typescript:  7.0.2 
    react: latest => 19.2.8 
    react-dom: latest => 19.2.8 
  1. On our project (I know you don't support v6, but you have exactly the same code in all higher versions):
System:
    OS: macOS 26.6.2
  Binaries:
    Node: 24.14.1 - /Users/romannakoval/.volta/tools/image/node/24.14.1/bin/node
    npm: 11.11.0 - /Users/romannakoval/.volta/tools/image/node/24.14.1/bin/npm
    pnpm: Not Found
  Browsers:
    Chrome: 152.0.7977.65
    Edge: Not Found
    Firefox: 154.0
    Safari: 26.6.2
  npmPackages:
    @emotion/react: 11.14.0 => 11.14.0 
    @emotion/styled: 11.14.0 => 11.14.0 
    @mui/core-downloads-tracker:  6.5.0 
    @mui/material: 6.5.0 => 6.5.0 
    @mui/private-theming:  6.4.9 
    @mui/styled-engine:  6.5.0 
    @mui/system: 6.5.0 => 6.5.0 
    @mui/types:  7.2.24 
    @mui/utils:  6.4.9 
    @types/react: 18.3.12 => 18.3.12 
    react: 18.3.1 => 18.3.1 
    react-dom: 18.3.1 => 18.3.1 
    typescript: 5.9.3 => 5.9.3 

Browsers: both FF and Chrome

Search keywords: Link, Typography, color, theme variant, white, CSS color, underline, alpha, getTextDecoration, unsupported color, ErrorBoundary, app crush

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions