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

Unified React theming with other CSS-in-JS libraries? #998

Closed
JReinhold opened this issue Nov 6, 2018 · 21 comments
Closed

Unified React theming with other CSS-in-JS libraries? #998

JReinhold opened this issue Nov 6, 2018 · 21 comments

Comments

@JReinhold
Copy link

  • emotion version: 9.2.12
  • react version: 16.5.2

The problem

Looking at #100 , it looks like it was agreed that Emotion would use the theming package to handle all theming in React. I can't really see any trace of this though. So my first question is:

Does emotion still use the unified theming approach, or has that been scrapped in favour of a custom solution?

The reason I'm asking, is because I assumed that when using the unified theme approach, the different CSS-in-JS libraries would be able to access the same theme from a single ThemeProvider. It might seem a bit weird to have multiple CSS-in-JS solutions in the same React app, but hear me out.

Motivation

We're using Material-UI for styling, which internally uses JSS as its CSS-in-JS solution. I don't feel the JSS api suits my needs as much as Emotion, so I actually prefer to use Emotion when I make my own custom styles.

However, I can't access the theme provided by Material-UI, because it uses JSS. (I know I can do some hacky stuff like having multiple ThemeProviders, and making sure they stay sync, but this is really an issue where I try to explore the idea of NOT doing that.) So whenever I need to apply styles from the theme, I have to use JSS for styling instead of Emotion.
It becomes a bit confusing in the long run, to use different styling techniques in different places all aorund the code.

The question

So, is there any way we could achieve this, using the unified theming approach? So using the same package as JSS and styled-components, to share themes across CSS-in-JS solutions?

Reproduction

I've made a simple CodeSandbox that demonstrates that emotion cannot read from a theme provided by JSS's <ThemeProvider />.

Edit wym8rkwq68

@mxstbr
Copy link
Contributor

mxstbr commented Nov 6, 2018

Probably relevant that emotion is planning on changing their theming API completely: #887 I doubt they'll switch to the theming package if they go ahead with that!

@JReinhold
Copy link
Author

Arh yeah, I see, then it definitely won't use the theming package.

This problem should maybe be solved as a separate package that works as an adapter between all the major CSS-in-JS theming solutions.

@mxstbr
Copy link
Contributor

mxstbr commented Nov 6, 2018

Imo ity shouldn't be solved that way. Just render two providers with your theme and be done with it, no need to change all CSS-in-JS frameworks:

<MUIProvider theme={theme}>
  <EmotionProvider theme={theme}>
    <App />
  </EmotionProvider>
</MUIProvider>

@JReinhold
Copy link
Author

Ha, you are absolutely right. At first I thought "well, that won't work if you change theme!"... But then I realised I was completely wrong, it will work fine. Thanks!

Sorry for the brainfart. I agree with @mxstbr, go ahead and close this issue. 🙂

And have a nice day! 👋

@oliviertassinari
Copy link

oliviertassinari commented Nov 13, 2018

@mxstbr's solution looks great!

By the way, I wish we could support different backends for @material-ui/styles. It's using JSS now, but I'm wondering if we couldn't support styled-components and emotion at the same time. Having two CSS-in-JS runtime is less than ideal. The main challenge seems to be with the composition of different class names.

@oliviertassinari
Copy link

@Andarist Hi, would you, or someone in the core team, be interested in helping us bringing an emotion backend to Material-UI?

@Andarist
Copy link
Member

Could u describe the goals of this?

@oliviertassinari
Copy link

oliviertassinari commented Nov 15, 2018

@Andarist The goal is to allow people to choose the CSS-in-JS runtime. We shouldn't force the usage of JSS and the bundle size overhead (15 KB) that comes with it if they are already using emotion in their codebase.

@Andarist
Copy link
Member

That would be cool, is there any page where it is described how to style material-ui options (from the user perspective)? Including ofc all options available - with described specificity, which styles win over which, how to do theming etc.

@oliviertassinari
Copy link

oliviertassinari commented Nov 16, 2018

@Andarist What I have in mind only involves our internal API, it should give us more freedom of action. We have the following resources:

Aside the bundle size deduplication, emotion has some additional pros: supports SSR streaming (++ for SEO), is smaller, seems to be faster at runtime server side (depends on the benchmark), has more community adoption, use hashing over index counter that is less error prone. It's a good candidate for being our default runtime.

Also, it's to be noted that I want to try a hook API migration on Material-UI side (instead of HOC), I think that it will provides better DX and performance.

@Andarist
Copy link
Member

Thanks @oliviertassinari for the links. I'll definitely look into it to see if we could prepare an emotion-flavour of material-ui.

@oliviertassinari
Copy link

@Andarist Awesome! I will look into it too. I need to get more familiar with emotion's entrails.

@gkohen
Copy link

gkohen commented Oct 9, 2019

We're debating on the same thing. @Andarist and @oliviertassinari, do you think feature is implemented these days in Material-UI?
https://material-ui.com/guides/interoperability/#emotion

@oliviertassinari
Copy link

@gkohen You can use two theme providers to inject the same MUI theme object. Would it work?

@Andarist
Copy link
Member

Andarist commented Oct 9, 2019

I haven't found time to dig into this (emotion-flavour of Material UI) - really wanted to, but other things got in the way.

You can use emotion with Material UI right now, but it costs you two css-in-js to be shipped.

@gkohen
Copy link

gkohen commented Oct 9, 2019

@Andarist , considering the size of much bigger artifacts such as an image, in our case an extra css-in-js is peanuts.
@oliviertassinari , I think it would work. Let's say I want to use some of the colors, or typography of the Material UI theme in a styled Emotion command. How I'd go about it? Is there any quick code sample you can point to?
It might be something more appropriate as a Material-UI git issue, but I'm on the fence here.
Thanks!

@gkohen
Copy link

gkohen commented Oct 9, 2019

Maybe I can answer my own question. How does the following look like:

import styled from "@emotion/styled";
import Button from "@material-ui/core/Button";

export default styled(Button)`
  && {
    color: white;
    background-color: blue;
    height: 80px;
  }
`;

@oliviertassinari
Copy link

oliviertassinari commented Oct 9, 2019

Maybe I can answer my own question. How does the following look like:

@gkohen https://material-ui.com/guides/interoperability/#themeprovider. I would be eager to find something more elegant!

@oliviertassinari
Copy link

oliviertassinari commented Oct 9, 2019

I would be eager to find something more elegant!

@gkohen So, right now, Material-UI documents the following:

import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import NoSsr from '@material-ui/core/NoSsr';
import { createMuiTheme, darken, fade } from '@material-ui/core/styles';

const defaultTheme = createMuiTheme();

const StyledButton = styled.button`
  padding: 8px 12px;
  border: 1px solid;
  cursor: pointer;
  outline: none;
  border-radius: ${props => props.theme.shape.borderRadius}px;
  color: ${props => props.theme.palette.primary.contrastText};
  background-color: ${props => props.theme.palette.primary.main};
  border-color: ${props => props.theme.palette.primary.main};
  transition: ${props => props.theme.transitions.create(['background-color', 'box-shadow'])};
  font-family: ${[
    '-apple-system',
    'BlinkMacSystemFont',
    '"Segoe UI"',
    'Roboto',
    '"Helvetica Neue"',
    'Arial',
    'sans-serif',
    '"Apple Color Emoji"',
    '"Segoe UI Emoji"',
    '"Segoe UI Symbol"',
  ].join(',')};
  &:hover {
    background-color: ${props => darken(props.theme.palette.primary.main, 0.1)};
    border-color: ${props => darken(props.theme.palette.primary.main, 0.2)};
  }
  &:active {
    background-color: ${props => darken(props.theme.palette.primary.main, 0.2)};
    border-color: ${props => darken(props.theme.palette.primary.main, 0.3)};
  }
  &:focus {
    box-shadow: 0 0 0 0.2rem ${props => fade(props.theme.palette.primary.main, 0.5)};
  }
  font-size: 18px;
  ${props => props.theme.breakpoints.up('md')} {
    font-size: 16px;
  }
`;

function StyledComponentsTheme() {
  return (
    <NoSsr>
      <ThemeProvider theme={defaultTheme}>
        <StyledButton>Styled Components</StyledButton>
      </ThemeProvider>
    </NoSsr>
  );
}

export default StyledComponentsTheme;

I'm not satisfied with it. I have spent some time to dig into the topic. I have found the following sources to benchmark against:

Leveraging this information, I'm wondering if we couldn't up with the following API. It's meant as a shortcut:

import { th } from '@material-ui/core';

const StyledButton = styled.button`
  padding: ${th.spacing(1, 1.5)};
  border: 1px solid;
  background-color: ${th.palette('primary')};
  color: ${th.get('palette.primary.main')};
  border-radius: ${props => props.theme.shape.borderRadius}px;
  transition: ${th.transitions.create(['background-color', 'box-shadow'])};
  ${th.breakpoints.up('md')} {
    font-size: 16px;
  }

My concern would then be at https://www.npmjs.com/search?q=keywords%3Astyled-components&ranking=popularity. It seems that most people either build internal tools for this problem or don't care. Do they write arrow function everytime they need to? Or do they go with a prop based approach, à la taildwind?

Update: looking at Primer, they might be a simpler way:

@oliviertassinari
Copy link

@gkohen What do you think of the following instead of the current demo?

import React from 'react';
import styled, { ThemeProvider } from 'styled-components';
import NoSsr from '@material-ui/core/NoSsr';
import { createMuiTheme, darken, fade } from '@material-ui/core/styles';

const defaultTheme = createMuiTheme();

const StyledButton = styled.button`
  ${({ theme }) => `
  padding: 8px 12px;
  border: 1px solid;
  cursor: pointer;
  outline: none;
  border-radius: ${theme.shape.borderRadius}px;
  color: ${theme.palette.primary.contrastText};
  background-color: ${theme.palette.primary.main};
  border-color: ${theme.palette.primary.main};
  transition: ${theme.transitions.create(['background-color', 'box-shadow'])};
  font-family: ${[
    '-apple-system',
    'BlinkMacSystemFont',
    '"Segoe UI"',
    'Roboto',
    '"Helvetica Neue"',
    'Arial',
    'sans-serif',
    '"Apple Color Emoji"',
    '"Segoe UI Emoji"',
    '"Segoe UI Symbol"',
  ].join(',')};
  &:hover {
    background-color: ${darken(theme.palette.primary.main, 0.1)};
    border-color: ${darken(theme.palette.primary.main, 0.2)};
  }
  &:active {
    background-color: ${darken(theme.palette.primary.main, 0.2)};
    border-color: ${darken(theme.palette.primary.main, 0.3)};
  }
  &:focus {
    box-shadow: 0 0 0 0.2rem ${fade(theme.palette.primary.main, 0.5)};
  }
  font-size: 18px;
  ${theme.breakpoints.up('md')} {
    font-size: 16px;
  }
  `}
`;

function StyledComponentsTheme() {
  return (
    <NoSsr>
      <ThemeProvider theme={defaultTheme}>
        <StyledButton>Styled Components</StyledButton>
      </ThemeProvider>
    </NoSsr>
  );
}

export default StyledComponentsTheme;

@oliviertassinari
Copy link

oliviertassinari commented Oct 18, 2019

The proposed change is going live in the documentation in the next release.

Related: reddit thread.

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

5 participants