Repository: mui/material-ui (TypeScript)
Date: 2021-06-02 07:49:24+00:00
Task type: Bug Fix

Issue:
[theme] Improve default theme dark colors
- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

The primary and secondary colors are identical between the light and dark theme. 

## Expected Behavior 🤔

The color should becomes less saturated as in the demos of the documentation and as specified by the Material Design specification. 

## Steps to Reproduce 🕹

https://codesandbox.io/s/material-ui-dark-theme-41nm4

## Your Environment 🌎

| Tech        | Version |
| ----------- | ------- |
| Material-UI | v4.7.2 |
| React       |   16.12.0    |
| Browser     |    Chrome Version 78.0.3904.108 (Official Build) (64-bit)     |

[TextField] red error text fails a11y contrast ratio requirement of 4.5:1
<!-- Provide a general summary of the issue in the Title above -->
Even on a white background, the error text for [TextField] is flagged at < 4.5:1 ratio, unacceptable for accessibility
<!--
  Thank you very much for contributing to Material-UI by creating an issue! ❤️
  To avoid duplicate issues we ask you to check off the following list.
-->
<!-- Checked checkbox should look like this: [x] -->

- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯
<!-- Describe what happens instead of the expected behavior. -->
The default red error text is too light against the best-case white background
## Expected Behavior 🤔
It should be at least 4.5:1 on a white background;  Default behavior on hover often darkens the background, requiring an even darker red to pass
<!-- Describe what should happen. -->

## Steps to Reproduce 🕹

<!--
  Provide a link to a live example (you can use codesandbox.io) and an unambiguous set of steps to reproduce this bug.
  Include code to reproduce, if relevant (which it most likely is).

  You should use the official codesandbox template as a starting point: https://material-ui.com/r/issue-template-next

  If you have an issue concerning TypeScript please start from this TypeScript playground: https://material-ui.com/r/ts-issue-template

  Issues without some form of live example have a longer response time.
-->

Steps:

1. https://next.material-ui.com/components/text-fields/
2. Go to Validation section where there's demo of red error text
3. I'll attach a pic where I open dev tools and use Lighthouse element inspector to inspect the text and it flags the contrast violation
<img width="315" alt="Screen Shot 2021-05-13 at 6 08 12 PM" src="https://user-images.githubusercontent.com/32496958/118206455-695ecc80-b417-11eb-8fd0-355a8bfbad9e.png">


## Context 🔦
a11y testing; conformance required by customers
<!--
  What are you trying to accomplish? How has this issue affected you?
  Providing context helps us come up with a solution that is most useful in the real world.
-->

## Your Environment 🌎
Your demo page (see url above, step 1); also happens on latest 4.x



Issue discussion:
The link inherits its color from the parent elements. You need to make sure the color change. You have a couple of options. You should be able to find a more detailed answer on StackOverflow or in this bug & feature tracker (I believe it's a duplicate).
I couldn't find the duplicated issue. So, you have the options between (by order of preference):
- Using CssBaseline component
- Using a wrapping Typography component with component div
- Setting the color prop on your very Link component 
@oliviertassinari If you take care to follow the link I put in *Steps to Reproduce*, you will see that using `CssBaseline` doesn't help. I put a bare minimum example at that link, that uses just `ThemeProvider,` `CssBaseline` and `Link.`  Therefore I don't have any parents which may interfere with the default styling. Aren't dark theme style supposed to apply to `Link` as well?
Oh, I see, then it's about picking a color with enough contrast :)
What do you think of the following?

![Capture d’écran 2019-12-11 à 15 32 34](https://user-images.githubusercontent.com/3165635/70630199-79271f80-1c2b-11ea-80f5-a87b27475716.png)

![Capture d’écran 2019-12-11 à 15 31 51](https://user-images.githubusercontent.com/3165635/70630151-5f85d800-1c2b-11ea-9b06-3fdb5ec69ced.png)

https://material.io/design/color/dark-theme.html

```diff
diff --git a/packages/material-ui/src/styles/createPalette.js b/packages/material-ui/src/styles/createPalette.js
index 4df6b5efc..b89dd0c19 100644
--- a/packages/material-ui/src/styles/createPalette.js
+++ b/packages/material-ui/src/styles/createPalette.js
@@ -79,16 +79,8 @@ function addLightOrDark(intent, direction, shade, tonalOffset) {

 export default function createPalette(palette) {
   const {
-    primary = {
-      light: indigo[300],
-      main: indigo[500],
-      dark: indigo[700],
-    },
-    secondary = {
-      light: pink.A200,
-      main: pink.A400,
-      dark: pink.A700,
-    },
+    primary: primaryOption,
+    secondary: secondaryOption,
     error = {
       light: red[300],
       main: red[500],
@@ -100,6 +92,32 @@ export default function createPalette(palette) {
     ...other
   } = palette;

+  const primary =
+    primaryOption || type === 'light'
+      ? {
+          light: indigo[300],
+          main: indigo[500],
+          dark: indigo[700],
+        }
+      : {
+          light: indigo[100],
+          main: indigo[200],
+          dark: indigo[300],
+        };
+
+  const secondary =
+    secondaryOption || type === 'light'
+      ? {
+          light: pink.A200,
+          main: pink.A400,
+          dark: pink.A700,
+        }
+      : {
+          light: pink[100],
+          main: pink[200],
+          dark: pink[300],
+        };
+
   // Use the same logic as
   // Bootstrap: https://github.com/twbs/bootstrap/blob/1d6e3710dd447de1a200f29e8fa521f8a0908f70/scss/_functions.scss#L59
   // and material-components-web https://github.com/material-components/material-components-web/blob/ac46b8863c4dab9fc22c4c662dc6bd1b65dd652f/packages/mdc-theme/_functions.scss#L54
```
Ran into the above stated issue of the Link object not respecting light and dark variants defined in the theme. Started on a PR for a possible fix.
@Davst what do you think of https://github.com/mui-org/material-ui/pull/19105#issuecomment-572995411
@oliviertassinari Ah yeah to clarify, I ran into part of this issue. In my case being that if you have manually defined a light: and dark: value for the primary or secondary color, the Link object will still just grab the main color.

The discussion you linked looks good but I realize that my issue might be better described in: https://github.com/mui-org/material-ui/issues/18831#issuecomment-569049005

But that ticket was closed, so this was the closest I could find to the same issue.

For clarity what I was planning on making a quick fix for was that Typography doesn't respect if you manually define light: and dark: variants to the primary or secondary color. This means that Link won't use your specified dark or light variants either, the problem as i see it lies in the following line:
https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Typography/Typography.js#L81

This and the secondary should be updated to check if there is a variant that corresponds to the type

The original ticket description outlines the same problem, some of the discussion on this issue has evolved beyond the main description to involve systematic approaches to color systems which is nice as well. At the current time, I just really need to fix the fact that Link and Typograpåhy ignores my defined colors.

If you want to separate the two issues, I could fix this to https://github.com/mui-org/material-ui/issues/18831#issuecomment-569049005 if you want to reopen that or something (not sure of the exact etiquette here :) )
> the Link object will still just grab the main color.

@Davst I think that the component should be the less dependent on the light or dark mode as possible, we are using color.main on purpose, I don't think that we should change it. The light, main, and dark keys are meant to be used at the same time. So yes, it's the expected behavior, if you need something different, you can change the main value right when you set the dark modality.
@oliviertassinari really? Say I use a setting to check for dark mode via system settings and show up with the light theme on a computer with light mode, but with the dark theme on their phone using dark mode.

Am I to understand that the expected behavior would be to render links possibly unreadable in one of the situations by using the main color which in my case works nicely for light but is garbage for dark mode instead of conforming to how the rest of the theme creation works where I can define the correct color to use for dark mode?

I must be missing something in how you mean to define the colors. To me this seems very odd and inconsistent with how themes are structured for the majority of other objects.

Could you provide an example of how you'd suggest to provide a user selectable, or system-setting-based dark/light mode toggle toggle that would allow to adapt the primary and secondary color? 

Also, wouldn't changing the main value when you set the modailty create problems with packaging the theme?
@Davst You can check how the documentation website solves this problem. 
https://github.com/mui-org/material-ui/blob/e5b2e229b5ed4366ba89d47c9a4295d234a74d7e/docs/src/modules/components/ThemeContext.js#L181-L204
Ok, thanks, I can use that.

Maybe an issue should be raised regarding the documentation website since as far as I understand from https://material-ui.com/customization/palette/ it indicates the usage I was looking to fix, which might be the source of people (and me) missunderstanding this.

This example: 
```
import { createMuiTheme } from '@material-ui/core/styles';

const theme = createMuiTheme({
  palette: {
    primary: {
      // light: will be calculated from palette.primary.main,
      main: '#ff4400',
      // dark: will be calculated from palette.primary.main,
      // contrastText: will be calculated to contrast with palette.primary.main
    },
    secondary: {
      light: '#0066ff',
      main: '#0044ff',
      // dark: will be calculated from palette.secondary.main,
      contrastText: '#ffcc00',
    },
    // Used by `getContrastText()` to maximize the contrast between
    // the background and the text.
    contrastThreshold: 3,
    // Used by the functions below to shift a color's luminance by approximately
    // two indexes within its tonal palette.
    // E.g., shift from Red 500 to Red 300 or Red 700.
    tonalOffset: 0.2,
  },
});
```
Here's a work-around I will do:  Add to createMuiTheme:
```
MuiFormLabel: {
            root: {
                "&$error": {
                    color: COLOR_DM_BG_LIGHTEST_RED,
                }
            }
        },
        MuiFormHelperText: {
            root: {
                "&$error": {
                    color: COLOR_DM_BG_LIGHTEST_RED,
                }
            }
        },
```
where: 
`export const COLOR_DM_BG_LIGHTEST_RED = "#DE3421";`

Solution patch:
diff --git a/docs/src/modules/components/MarkdownElement.js b/docs/src/modules/components/MarkdownElement.js
--- a/docs/src/modules/components/MarkdownElement.js
+++ b/docs/src/modules/components/MarkdownElement.js
@@ -2,7 +2,7 @@ import * as React from 'react';
 import PropTypes from 'prop-types';
 import clsx from 'clsx';
 import { makeStyles } from '@material-ui/styles';
-import { createTheme } from '@material-ui/core/styles';
+import { createTheme, alpha, darken } from '@material-ui/core/styles';
 
 const styles = (theme) => ({
   root: {
@@ -36,7 +36,9 @@ const styles = (theme) => ({
       padding: '0 3px',
       color: theme.palette.text.primary,
       backgroundColor:
-        theme.palette.mode === 'light' ? 'rgba(255, 229, 100, 0.2)' : 'rgba(255, 229, 100, 0.2)',
+        theme.palette.mode === 'light'
+          ? 'rgba(255, 229, 100, 0.2)'
+          : alpha(theme.palette.primary.main, 0.08),
       fontSize: '.85em',
       borderRadius: 2,
     },
@@ -176,6 +178,12 @@ const styles = (theme) => ({
         textDecoration: 'underline',
       },
     },
+    '& a code': {
+      color:
+        theme.palette.mode === 'dark'
+          ? theme.palette.primary.main
+          : darken(theme.palette.primary.main, 0.04),
+    },
     '& img, video': {
       maxWidth: '100%',
     },
diff --git a/packages/material-ui/src/styles/createPalette.js b/packages/material-ui/src/styles/createPalette.js
--- a/packages/material-ui/src/styles/createPalette.js
+++ b/packages/material-ui/src/styles/createPalette.js
@@ -2,8 +2,8 @@ import { deepmerge } from '@material-ui/utils';
 import MuiError from '@material-ui/utils/macros/MuiError.macro';
 import common from '../colors/common';
 import grey from '../colors/grey';
-import indigo from '../colors/indigo';
-import pink from '../colors/pink';
+import purple from '../colors/purple';
+import cyan from '../colors/cyan';
 import red from '../colors/red';
 import orange from '../colors/orange';
 import blue from '../colors/blue';
@@ -91,32 +91,62 @@ function addLightOrDark(intent, direction, shade, tonalOffset) {
   }
 }
 
-export default function createPalette(palette) {
-  const {
-    primary = {
-      light: indigo[300],
-      main: indigo[500],
-      dark: indigo[700],
-    },
-    secondary = {
-      light: pink.A200,
-      main: pink.A400,
-      dark: pink.A700,
-    },
-    error = {
-      light: red[300],
+function getDefaultPrimary(mode = 'light') {
+  if (mode === 'dark') {
+    return {
+      main: blue[200],
+      light: blue[50],
+      dark: blue[400],
+    };
+  }
+  return {
+    main: blue[700],
+    light: blue[400],
+    dark: blue[800],
+  };
+}
+
+function getDefaultSecondary(mode = 'light') {
+  if (mode === 'dark') {
+    return {
+      main: purple[200],
+      light: purple[50],
+      dark: purple[400],
+    };
+  }
+  return {
+    main: purple[500],
+    light: purple[300],
+    dark: purple[700],
+  };
+}
+
+function getDefaultError(mode = 'light') {
+  if (mode === 'dark') {
+    return {
       main: red[500],
+      light: red[300],
       dark: red[700],
-    },
+    };
+  }
+  return {
+    main: red[700],
+    light: red[400],
+    dark: red[800],
+  };
+}
+
+export default function createPalette(palette) {
+  const {
     warning = {
       light: orange[300],
       main: orange[500],
       dark: orange[700],
     },
     info = {
-      light: blue[300],
-      main: blue[500],
-      dark: blue[700],
+      light: cyan[300],
+      main: cyan[500],
+      dark: cyan[700],
     },
     success = {
       light: green[300],
@@ -129,6 +159,10 @@ export default function createPalette(palette) {
     ...other
   } = palette;
 
+  const primary = palette.primary || getDefaultPrimary(mode);
+  const secondary = palette.secondary || getDefaultSecondary(mode);
+  const error = palette.error || getDefaultError(mode);
+
   // Use the same logic as
   // Bootstrap: https://github.com/twbs/bootstrap/blob/1d6e3710dd447de1a200f29e8fa521f8a0908f70/scss/_functions.scss#L59
   // and material-components-web https://github.com/material-components/material-components-web/blob/ac46b8863c4dab9fc22c4c662dc6bd1b65dd652f/packages/mdc-theme/_functions.scss#L54


Test patch:
diff --git a/packages/material-ui/src/FormLabel/FormLabel.test.js b/packages/material-ui/src/FormLabel/FormLabel.test.js
--- a/packages/material-ui/src/FormLabel/FormLabel.test.js
+++ b/packages/material-ui/src/FormLabel/FormLabel.test.js
@@ -4,6 +4,8 @@ import { expect } from 'chai';
 import { createMount, describeConformanceV5, act, createClientRender } from 'test/utils';
 import FormLabel, { formLabelClasses as classes } from '@material-ui/core/FormLabel';
 import FormControl, { useFormControl } from '@material-ui/core/FormControl';
+import { hexToRgb } from '@material-ui/core/styles';
+import defaultTheme from '../styles/defaultTheme';
 
 describe('<FormLabel />', () => {
   const render = createClientRender();
@@ -165,7 +167,9 @@ describe('<FormLabel />', () => {
         <FormLabel data-testid="FormLabel" color="secondary" focused />,
       );
       expect(container.querySelector(`.${classes.colorSecondary}`)).to.have.class(classes.focused);
-      expect(getByTestId('FormLabel')).toHaveComputedStyle({ color: 'rgb(245, 0, 87)' });
+      expect(getByTestId('FormLabel')).toHaveComputedStyle({
+        color: hexToRgb(defaultTheme.palette.secondary.main),
+      });
     });
 
     it('should have the error class and style, even when focused', () => {
@@ -173,7 +177,9 @@ describe('<FormLabel />', () => {
         <FormLabel data-testid="FormLabel" color="secondary" focused error />,
       );
       expect(container.querySelector(`.${classes.colorSecondary}`)).to.have.class(classes.error);
-      expect(getByTestId('FormLabel')).toHaveComputedStyle({ color: 'rgb(244, 67, 54)' });
+      expect(getByTestId('FormLabel')).toHaveComputedStyle({
+        color: hexToRgb(defaultTheme.palette.error.main),
+      });
     });
   });
 });
diff --git a/packages/material-ui/src/styles/createPalette.test.js b/packages/material-ui/src/styles/createPalette.test.js
--- a/packages/material-ui/src/styles/createPalette.test.js
+++ b/packages/material-ui/src/styles/createPalette.test.js
@@ -1,5 +1,5 @@
 import { expect } from 'chai';
-import { deepOrange, indigo, pink } from '../colors';
+import { deepOrange, blue, purple, indigo } from '../colors';
 import { darken, lighten } from './colorManipulator';
 import createPalette, { dark, light } from './createPalette';
 
@@ -85,11 +85,11 @@ describe('createPalette()', () => {
 
   it('should create a dark palette', () => {
     const palette = createPalette({ mode: 'dark' });
-    expect(palette.primary.main, 'should use indigo as the default primary color').to.equal(
-      indigo[500],
+    expect(palette.primary.main, 'should use blue as the default primary color').to.equal(
+      blue[200],
     );
-    expect(palette.secondary.main, 'should use pink as the default secondary color').to.equal(
-      pink.A400,
+    expect(palette.secondary.main, 'should use purple as the default secondary color').to.equal(
+      purple[200],
     );
     expect(palette.text, 'should use dark theme text').to.equal(dark.text);
   });
@@ -176,8 +176,9 @@ describe('createPalette()', () => {
           contrastThreshold: 0,
         }));
       }).toErrorDev([
-        'falls below the WCAG recommended absolute minimum contrast ratio of 3:1',
-        'falls below the WCAG recommended absolute minimum contrast ratio of 3:1',
+        'falls below the WCAG recommended absolute minimum contrast ratio of 3:1', // warning palette
+        'falls below the WCAG recommended absolute minimum contrast ratio of 3:1', // info palette
+        'falls below the WCAG recommended absolute minimum contrast ratio of 3:1', // success palette
       ]);
 
       expect(() => {