Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] CSS variables support #21

Open
siriwatknp opened this issue Aug 7, 2021 · 0 comments
Open

[RFC] CSS variables support #21

siriwatknp opened this issue Aug 7, 2021 · 0 comments

Comments

@siriwatknp
Copy link
Owner

siriwatknp commented Aug 7, 2021

Problems

  • Dark theme flash on SSR (server-side rendering)
    related issues: #15588 #21371. One obvious example is material-ui documentation. If you toggle dark theme and press hard refresh, you will see a flash of light mode before it turns dark.

  • Bad debugging experience
    the value in dev tool is the color code ex. #f5f5f5 which is hard to know which design token the component is using. This gives bad experience to both developer (working locally) and non-developer (checking the color in production website)

    Screen Shot 2564-08-08 at 14 20 45

    What is the variable that the component is using? no idea unless look at the code.

  • Performance
    the current implementation consider light & dark as a separate theme which means by toggling to another mode will cause component in the tree to rerender. (However, the impact is not significant ~100-300ms depends on how large the page is)

Solution

CSS variables is the main ingredient to solve the above issues. Another important ingredient is to apply all the themes at once. How?

Case I: JS is disabled on the client

This could happen when user intentionally disable JS or internet connection is bad and could load only the HTML and CSS.
This is a very first crucial step to have perfect dark mode because if we can find a solution within this constraint, then we can use javascript to add more feature on top of it.

Since we cannot use JS to calculate stuff, everything should be ready on the server. The CSS should look like this.

// variable names and `:root` is for demonstration purpose
:root {
  --bg: #fff;
  --text: #000;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000;
    --text: #fff;
  }
}

If user does not set prefers scheme, they will see the site on light mode. But with prefer scheme dark enabled, the @media (prefers-color-scheme: dark) will have more specificity and override the root.

Components implementation should use the variables that are agnostic of schemes, meaning the variables should be the same across light & dark mode.

{
  backgroundColor: 'var(--primary-main)',
  color: 'var(--text-primary)'
}

Case II: JS available on the client

User will be able to select the theme they like, ex. light dark or system and the site will remember the user's preference.

To achieve this, an extra script need to be run on the client before the main react script to check selected theme in local storage. If theme (string) exists in local storage, it will apply the class, or attribute to the html or body. The snippet looks something like

(function() { try {
  var mode = localStorage.getItem('mui-mode');
  if (mode) {
    document.body.classList.add('mui-' + mode);
  }
} catch (e) {} })();

This script need to be placed at the top in the

The generated CSS at build time should contains .mui-dark specificity, otherwise it will not work.

// generated at build time
// variable names and `:root` is for demonstration purpose
:root {
  --bg: #fff;
  --text: #000;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000;
    --text: #fff;
  }
}
body.mui-dark {
  --bg: #fff;
  --text: #000;
}

Benefits

CSS Variables open many doors for improving DX and design opportunities.

  • fix flash dark mode on SSR (with the correct implementation)
  • multiple themes not only light & dark (ex, trueBlack comfort)
  • contextual styles without using javascript (<ThemeProvider theme={darkTheme}>
    // the naming is for demonstration purpose
    ...
    <div mui-mode="dark">
      <Button></Button>
      <Card>...</Card>
    </div>
    the content inside <div mui-mode="dark"> will behave as dark mode regardless of user's preference.
  • better debugging experience (anyone in the teams including non-devs can see the design tokens directly in the website without looking at the code.
  • improve performance, the toggle between themes still require javascript but does not cause the whole application to rerender.

Implementation

The challenge of css variables support is the integration with both the 2nd design system we are working on and the existing material-design components (aka @material-ui/core at the time of writing). So to make it easier to maintain and able work along side with other product, the css variables support should be a dedicated module which has only a few functionalities such as

  • turn theme object in React context into css variables
  • provide the context and function that switch between theme
  • provide APIs that determine theme for SSR (the script that need to be placed at the top of <body>

As a result, each design system can use the module to implement on its own.

Plan

  • create an unstable_api in system (could be moved to its own package later).
  • 2nd design system starts to consume the apis.
  • @material-ui/core (after the module is settled)
    • improve the colors palette without breaking change, hopefully (in order to remove runtime calculation such as alpha lighten, ...)
    • expose another Provider that consume CSS variables module
    • convert components to use cssVars (names might be changed)
      // Button
      const Button = styled('button')(({ theme: { cssVars, ...rest } }) => {
        const theme = cssVars || rest;
        // the implementation would not change much, and allow developer to migrate smoothly.
        return {
          borderRadius: theme.shape.borderRadius,
          ...
        }
      })

Resources & Inspiration

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant