[RFC] v5 styling solution š
See original GitHub issueThis 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?
- Maintaining & developing a great styling engine takes a considerable amount of time. We have experienced it first hand. Over the last 12 months, we have preferred to invest time on our core value proposition: the UI components, rather than improve the style engine. Working on it has a high opportunity cost.
- We have been facing issues with supporting dynamic styles for the components. The performance of our custom dynamic styles implementation (based on props) isnāt great (see the performance benchmarks below). This is seriously limiting the quality of the Developer Experience we can provide. Itās a blocker for improving our API around customizability or ease of writing styles. For instance, it will unlock: style utils props, color variant, and custom variant.
- The React community, at large, hasnāt voted for using JSS at scale (JSS is great and still used). 3 years ago we bet on the best option available. We have to recognize better options are available now. We can move faster and unlock better DX/UX by building on top of a more popular, existing, styling solution.
- Many developers use styled-components to override Material-UIās styles. End-users find themselves with two CSS-in-JS libraries in their bundle. Not great. It would be better if we could offer different adapters for different CSS-in-JS libraries. (Potential problems: we may need to re-write the core styles to match the syntax of the engine used š¤·āāļø)
What are the requirements?
Whatever styling engine we choose to go with we have to consider the following factors:
- performance: the faster the better but we are willing to trade some performance to improve the DX.
- bundle size: below our current 14.3 kB gzipped would be great.
- support concurrent mode:
@material-ui/styles
has partial support as Iām writing. - support SSR
- simple customization
- allow dynamic styling
- good community size
- theming
- flat specificity
- RTL
- TypeScript
It would be nice if it can support the following:
- zero-config from the perspective of Material-UI consumers
- streaming https://github.com/mui-org/material-ui/issues/8503
- source map
What are our options?
- styled-components
- emotion
- JSS (currently wrapped in material-ui)
- styletron
- Aphrodite
- fela
- else?
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: https://github.com/mnajdova/react-native-web/pull/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:
- styled-components
- emotion
- Aphrodite
JSS- react-styletron
fela
Dynamic props
Based on the open issues, it seems that Aphrodite doesnāt support dynamic props: https://github.com/Khan/aphrodite/issues/141 which in my opinion means that we should drop that one from our options too, leaving us with:
- styled-components
- emotion
Aphrodite- react-styletron
npm
While styled-components
and emotion
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.
- styled-components
- emotion
react-styletron
Support concurrent mode
- emotion: YES. Since v10 it is strict mode compatible based on their announcement post. I have tested it on a simple project that works as expected.
- styled-components: Partial. There is at least one bug with global styles in strict mode.
SSR
- emotion: YES. https://emotion.sh/docs/ssr. Also has an interesting no configuration support for prototyping only.
- styled-components: YES. https://styled-components.com/docs/advanced
Stars
- styled-components: 30.6k
- emotion: 11.4k
JSS: 5.9k
Trafic on the documentation
SimilarWeb estimated sessions/month:
sass-lang.com: ~476K/month (for comparison)- styled-components.com: ~239K/month
- emotion.sh: ~59K/month
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
- emotion: modern evergreen browsers + IE11
- styled-components: not documented for v5, but the previous versions support the following
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:
- Has the biggest community, people love to use it.
- Performance starting from v5 is good.
Cons:
- It will mean that all components styles need to be created using the
styled
API, which means for developers they will always have wrapper components if they need to re-style. - Lack of full concurrent support, which may create blockers down the road.
emotion
Pros:
- Relatively large community, growing.
- Good performance.
- Concurrent mode + SSR would be possible out of the box.
- The CSS prop can be useful for overrides.
- Source map support.
- A bit smaller.
Cons:
- The default SSR API should likely be discouraged for the advanced API instead.
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 - useSliderClasses
Conclusion
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?
Issue Analytics
- State:
- Created 3 years ago
- Reactions:275
- Comments:239 (128 by maintainers)
Top GitHub Comments
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.
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