-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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] v5 styling solution 💅 #22342
Comments
This comment has been minimized.
This comment has been minimized.
Emotion sounds great! The last bit got me! I'd love helping by doing this behind a beta flag or develop on some features:
I also noted that the theme object is going to stay the same, the - in my opinion - the absolute best way to theme an application! I have nothing else to say 😲 Thanks for the great work on M-UI. I love the direction the project is going. Moving to a more standardized way of styling is the way to go. I know the team and community will work out the kinks, and v5 - by the sounds of it - is going to be even more awesome! 🚀 |
As someone who has used both styled-components and emotion I can confirm transitioning between them is easy and painless. + emotion is more typescript friendly |
Speaking as Emotion's maintainer - this sounds great 🚀 We should also be able to release a new major soon-ish which won't be any revolution, just some cleanups, overall improvements, hooks under the hood and TS types improvements (better inference and performance). Oh, and rewritten parser! that eliminates some edge bugs in Emotion and Styled-Components (as currently, they are both using the same parser). It is both smaller and faster. |
What about breaking changes, which option introduce more breaking changes (if any)? |
Not sure if it makes a difference but were the benchmarks done with emotion and/or style-component's babel plugins? They help to optimize things further. |
It was my understanding that MUI had previously indicated it was going with styled so this is a surprise. I think emotion is a great library, but with more people using styled currently it's important you find ways to give them good options if they don't want to migrate to emotion |
@ee0pdt Emotion is very, very much like styled. I think that's part of the whole choice for Emotion over styled-components. There are clear benefits, and migration debt is little to none. And there's a whole section about supporting both by giving a choice to developers. That could be a way to go, but then again, standardizing would probably help future us more. The full concurrency and no wrapper components is a deal-breaker for me. I get that others might want something styled provides, and that should be considered. I'd rather push toward standardization |
Why was styletron-react ruled out? It leaves out a whole metric that wasn't considered, which is memory consumption. The default styletron engine (and fela) is atomic. Whilst a bit slower it saves a lot of memory. Having seen a lot of react pages do nothing and go >1GB after a while it's a bit of a concern. The browser freezes after that. With an atomic framework performance improves over time globally, across components as each atomic "class" is cached. Likely not reflected in the test either. |
Happy with either as long as it supports SSR |
I just withdrew my vote from the original styled components issue 😅 - first learned to know emotion through storybook, but |
Thanks everyone for the quick feedback. Here are answers to some of the comments/questions.
@sag1v using // previosly
root: {
contained: {
'&$disabled': { // <-- this part will need to be transformed
color: 'red',
},
},
containedPrimary: {
color: 'blue',
},
}
// after
root: {
contained: {
'&.Mui-disabled': {
color: 'red',
},
},
} However as the styles syntax between
@hc-codersatlas nope, but the perfs are those are anyway between the top few, so I don't believe it would make any difference. Good call tough!
My comments around why Also atomic css may cause issues with overrides, as each classname contains only one styler rule.
You need to add it if you are using the |
It is possible to skip adding JSX pragma, but it requires extra Babel setup and it comes with worse support from the tooling - for example, TS is not able to type-check your CSS prop as accurately as it does when using JSX pragma. More information can be found here: https://github.com/emotion-js/emotion/pull/1941/files#diff-9abe25e5d2b00958d4b9849f5f20c139R5 |
@mnajdova thanks. I was just hoping memory usage is covered more than vouching for styletron in particular. As to downloads or community - "only" Uber uses styletron :) so no worries. Voted for emotion in the first place. Would be cool if there was a babel plugin or similar that can transform static styles to real css classes. There's already a similar library called |
To be useful Fela will require a set of plugins like |
My only concern is having to use Most of the time, we try to use Hopefully, there is no breaking change for those two APIs, and the I am not forced to use |
@yordis You definitely won't be forced to use the |
👍 the choice to use Emotion. Avoiding the |
Regarding these facts:
If styled-components had full support for concurrent mode, would it be a more sensible choice, given most developers are overriding MUI with styled-components (excluding JSS)? The point about emotion being smaller in bundle size is moot if 2 css-in-js solutions need to be included. And I would presume most practical applications of MUI involve overriding its styles. |
@petermikitsh the reasons why we concluded on emotion are actually the four points in the conclusion
Having the first point in mind, if developers really want to avoid having two css-in-js solutions in the bundle, the migration cost is really small for moving to emotion + it supports different API other than the |
@ko-toss thanks for writing this up these are all good points. The fact that emotion generates one className with all styles, makes it the better engine for resolving overrides. The problem we may have with generated multiple classNames is that the last class written will win, which may become problematic in the future. On another project, I was using atomic css, where from memory consumption is much better because all css rules are written only once, but doing predictable overrides there is pretty hard, as each className is one atomic rule, and than again which one wins, depends on the order of which they are written, if you do not decide to previously process all styles and merge them correctly, which may impact perf in the end. On the other hand, I believe that using any css-in-js solution, people will not just randomly create infinite combination of styles, they are still pretty structured based on the props value. However, again these are good points, that we will have in mind, thanks a lot for sharing them 👍 |
(this is rather FYI, emotion is a good choice) https://github.com/cristianbote/goober (1kB, perf little worse than emotion) I have no experience with that yet but I want to try it one day. |
@cztomsik Similar to https://github.com/kuldeepkeshwar/filbert-js but has no JavaScript syntax support (only CSS template string) |
Same way as you do now, it's still a hook even though it's returned from a function. const makeStyles = styles => props => {
const theme = useTheme()
// ...
} |
So I'm currently fiddling around with the new styling philosophy of v5 and I can't really see how I should solve this problem: <ListItem
key="myKey"
button
exact
to="/"
component={NavLink}
>
<ListItemText primary="Home" />
</ListItem> This returns html like this: <a href="/" ... ><div><span>Home</span></div></a> So if I now want to use the In v4 world, I'd have done it like this: const useStyles = makeStyles({
activeNavLink: {
"> span": {
fontWeight: "bold"
}
}
});
<ListItem
key="myKey"
button
exact
to="/"
component={NavLink}
activeClassName={classes.activeNavLink}
>
<ListItemText primary="Home" />
</ListItem> I know there are different ways to solve this. But how to approach this particular use case? |
@lud-hu please create an issue with a codesandbox link where we can take a look at the specific scenario. You can probably do something similar to:
|
@mnajdova Thanks! <ListItem
key="myKey"
button
exact
to="/"
component={NavLink}
sx={{
'&.custom-dense-class': { '& span': { fontWeight: 'bold' } },
}}
activeClassName='custom-dense-class'
>
<ListItemText primary="Home" />
</ListItem> I guess it needs some time to get used to the new mechanism. |
Pretty sad to see that JSS is no longer supported |
@cristobalchao You can checkout tss-react. |
It's still supported - you can use whichever styling solution you chose in your app and to restyle MUI components. It's just more efficient for bundle size to use the same as the MUI components. |
|
A question using like non primivites (object, functions and arrays etc) has always been tricky in React performance. I've recently went over to v5 and saw the By passing new objects all the time it won't be easy to add |
@EloB there is no magic, nothing is done. If you notice perf issues on a component, you can memoize the object inside the |
@mnajdova At that point it's often really late. When a complete app built the "wrong way" you need to refactor a lot of code. These type of performance issues often happens in the "late game" and are really hard to fix at that stage. For me React development is like game development in a sense. Where game developers historically looked a lot on amount of polygons and where React developers have to think about amount of virtual renders. Game developers don't think about that when the product was "finished". It was part of their everyday thoughts. React on small scale is rarely a problem same as games. These types of issues occur when you move from being a prototype to a fullscale enterprise app. All the wrong doing is hard to correct at that stage and also hard to pinpoint because you suddenly "hit the roof" of what the software/hardware could handle (also often discovered by end-users because they don't have the expensive laptops that developers have) and at that point doesn't exists a easy/quick fix. So documenating best practice would be really nice :) Don't get me wrong! I love material ui and want it to be part of everything I do. It helped me a lot but it would be nice if it was self educational by using it. Like follow these patterns and you won't get these problems in the end when using |
This comment has been minimized.
This comment has been minimized.
@garronej I often create something like that. I've been working with Smart TV applications where hardware/software are really crap. So I've been really interested in performance and React. I tend to create all my callbacks inside a // helpers
const getValues = (...setters) => setters.map((setter) => {
let value;
setter(current => {
value = current;
return current;
});
return value;
});
const update = (setter, states) => setter((current) => {
const merged = { ...current, ...states };
return shallowEqual(current, merged) ? current : merged;
});
// used like
const [state, setState] = useState();
const handles = useMemo(() => ({
handleUsingState: () => {
const [currentState] = getValues(setState);
console.log(currentState);
},
handleUsingRef: () => console.log(refProp.current),
}), []); Also only use one const [state1, setState1] = useState(null);
const [state2, setState2] = useState(null);
// Better todo something like this so you can change multiple states at the same time
const [state, setState] = useState(defaultState); // { state1: null, state2: null }
// Because
<button onClick={() => {
// Will trigger one virtual render
setState1("one1");
setState2("two1");
setTimeout(() => {
// and this will trigger two virtual renders because it's not in a react triggered event loop
// even if the timeout was created during a react event loop but that isn't something that react keeps track of
setState1("one2");
setState2("two2");
// setState({ state1: "one2", state2: "two2" }) would always trigger one virtual render
// or using a helper like that update function. To prevent even further rerenders if nothing changed.
// update(setState, { state1: "one2", state2: "two2" })
});
}} /> This is a bit of side track from the initial conversation 😅 |
@EloB If you want to optimize the styles more, you could try to use global css variables:
And put this element inside some top-level component. Then you can use everywhere the styles like this:
Or in styles:
Thus you don't need to generate dynamically the theme object for your styles and you get better performance. |
@a-tonchev Cool idea! How is the support of css variables? |
@EloB all except IE supports them https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties |
@EloB while I prefer the |
It won't be supported after v5 and to be honest the options to use instead are not convincing me, an example is "style-components", IMO from a development experience doesn't seem right to me. I might need to start looking somewhere else |
We finally managed to come up with a nested selector API that is much closer to how it worked in v4's demo_nested_selectors_withStyles_tss.mp4The new changes should be merged soon, after that I should be able to start working on the codemod for migration from @material-ui/styles's makeStyles to tss-react |
@garronej nice! This is much closer to how it was in v4. Great work 🎉 Looking forward to the codemod, I guess lots of people that haven't migrated yet, would be able to do it more easily after it is available. Thanks 🙏 |
Could the documentation on this change perhaps be improved? As is, I find it very difficult to distinguish what is now preferred and what is legacy. In particular, is the use of I'm also wondering about the compatibility with other frameworks. For instance, I thought that in order to express relationships such as "make an element visible or change color when the user hovers over it" I would need to have the ability use the components selector API. According to How to use the components selector API this means I need to use a custom babel configuration even when using MUI's default styling solution, I would much prefer to stick with the default configuration and recommended options in all of the frameworks and libraries I'm integrating. |
@godmar thanks for your feedback. For sure, we need to work on the documentation. @danilo-leal had a great suggestion on how we may approach this. As for now, everything under the |
Thank you. I'm still confused, though. For instance, Styled components API is under the "Styles (legacy) -> basics" drawer and describes the use of However, under System -> styled -> API here it describes the use of Side by side: Under the documentation portion that you say is recommended (under System ->
Yet, elsewhere it says:
|
@godmar the What we use in v5, and recommend developers to use are the utilities from |
This RFC is a proposal for changing the styling solution of Material-UI in v5.
TL:DR; the core team proposes we go with emotion
What's the problem?
What are the requirements?
Whatever styling engine we choose to go with we have to consider the following factors:
@material-ui/styles
has partial support as I'm writing.It would be nice if it can support the following:
What are our options?
Comparison
Performance
Here are benchmarks with dynamic styles of several popular libraries (note the Material-UI v4 only use static styles which have good performance):
PR for reference: mnajdova/react-native-web#1
Based on the performance, I think that we should eliminate: JSS (currently wrapped in @material-ui/styles), styletron, and fela. That would leave us with:
JSSfelaDynamic props
Based on the open issues, it seems that Aphrodite doesn't support dynamic props: Khan/aphrodite#141
which in my opinion means that we should drop that one from our options too, leaving us with:
Aphroditenpm
While
styled-components
andemotion
are both libraries are pretty popular,react-styletron
at the time or writing is much behind with around 12500 downloads per week (this in my opinion is a strong reason why we should eliminate it, as if we decide to go with it, the community will again need to have two different styling engine in their apps).Here is the list rang by the number of Weekly downloads at the time of writing:
Note that storybook has a dependency on emotion. It significantly skews the stats.
react-styletronSupport concurrent mode
SSR
Stars
JSS: 5.9kTrafic on the documentation
SimilarWeb estimated sessions/month:
sass-lang.com: ~476K/month (for comparison)cssinjs.org: <30k/month (for comparison)Users feedback
Based on the survey, 53.8% percent are using the Material-UI styles (JSS), which is not a surprise as it is the engine coming from Material-UI. However, we can see that 20.4% percent are already using styled-components, which is a big number considering that we don't have direct support for it. Emotion is used by around 1.9% percent of the developers currently based on the survey.
Having these numbers we want to push with better support for styled-components, so this is something we should consider.
Browser support
Bundle size
What's the best option?
Default engine
Even if we decide to support multiple engines, we would still need to advocate for one by default and have one documented in the demos.
styled-components
Pros:
Cons:
styled
API, which means for developers they will always have wrapper components if they need to re-style.emotion
Pros:
Cons:
Support multiple
We may try to support multiple CSS-in-JS solutions, by providing our in house adapters for them. Some things that we need to consider is that, that we may have duplicate work on the styles, as the syntax is different between them (at least jss compared to styled-components/emotion). We will reuse the theme object no matter what solution we will pick up.
The less involved support for this may come from the usage of the
styled
, as people may do some webpack config to decide which one to use - (this is just something to consider).Additional comments
Deterministic classnames on the components that can be targeted for custom styles
Regarding how the classes look and how developers may target them, I want to show a comparison of what we currently have and how the problem can be solved with the new approach.
As an example, I will take the Slider component. Here is currently how the generated DOM look like:
Each of the classes has a very well descriptive semantic and people can use these classes for overriding the styles of the component.
On the other hand, emotion, styled-components or any other similar library will create some hash as a class name. For us to solve this and offer the developers the same functionality for targeting classes, each of the components will add classes that can be targeted by the developers based on the props.
This would mean that apart from the classes generated by emotion, each component will still have the classes that we had previously, like
MuiSlider-root
&MuiSlider-colorPrimary
, the only difference would be that this classes will now be used purely as selectors, rather than defining the styles for the components. This could be implemented like a hook - useSliderClassesConclusion
No matter which solution we would choose, we would use the
styled
API, which is supported by the two of them. This will allow us down the road to have easier support for styled + unstyled components (probably with webpack aliases, like for using preact).After we investigated the two options we had in the end, the core team proposes we go with emotion. Some key elements:
A small migration cost between styled-components and emotion
Developers already using styled-components should be able to use emotion with almost no effort.
There are different ways for adding overrides other than wrapper components
The support of cx + css from emotion can be beneficial for developers to use it as an alternative for adding style overrides if they don't want to create wrapper components.
Concurrent mode is for sure supported 👍
Kudos to @ryancogswell for doing a deeper investigation on this topic. So far we did not find anything in @emotion's code that would give us concern that concurrent mode wouldn't work.
We were also looking into
createGlobalStyle
from styled-components as a comparison to emotion's Global component. It is doing most of its work during render (inherently problematic for Strict/Concurrent Mode) and just using useEffect for removing styles in its cleanup function. createGlobalStyle needs a complete rewrite before it will be usable in concurrent mode -- it isn't OK for it to add styles during render if that render is never committed. It looks like someone has tried rewriting it with some further changes in the last month, so we will need to follow this progress.How is the specificity handled
Emotion's docs recommend doing composition of CSS into a single class rather than trying to leverage styles from multiple class names. In v5, our existing global class names would be applied without any styles attached to them. The composition of emotion-styled components automatically combines the styles into a single class. This potentially gets rid of these stylesheet order issues at least internal to the styles defined by Material-UI, because every component's styles are driven by a single class name 👍.
So we would have the global class names (for developers to target in various ways for customizations) and then a single generated (by emotion) class name per element that would consolidate all the CSS sources flowing into it.
Specificity is then handled by emotion based on the order of composition.
All compositions using emotion (whether render-time or definition-time composition) results in a single class on the element.
styled-components does NOT work this way concerning render-time composition (definition-time composition does get combined into a single class). The same composition in styled-components results in multiple classes applied to the same element and the specificity does not work as I would have intended.
Alternatives
What do you think about it?
The text was updated successfully, but these errors were encountered: