This repo uses a CSS-in-JS library called Emotion for its styling.
Emotion is a performant and flexible CSS-in-JS library. Building on many other CSS-in-JS libraries, it allows us to style apps quickly with string or object styles. It has predictable composition to avoid specificity issues in CSS. With source maps and labels, Emotion has a great developer experience and performance with heavy caching in production.
Also, Material UI v5 will most likely use Emotion instead of JSS:
material-ui - [RFC] v5 styling solution
- Use the
css
prop with template literals (don't usestyled
). - Define styling outside of the component.
- Less clutter, easier to maintain and refactor.
- Makes it easier if we'd like to change our styling library if needed.
- Prefer creating small styling blocks instead of using CSS selectors.
- Keeps the code modular and avoid specificity issues,
- CSS selectors are still useable, but note that they affect the entire sub-tree.
import { css } from '@emotion/react';
import { colors } from 'styles/theme';
const someComponentCss = css`
background-color: ${colors.offWhite};
/* Will affect all descendent "a" elements, use only when it's safe */
a {
color: ${colors.brandPrimary};
}
`;
const titleCss = css`
font-size: 3rem;
`;
function SomeComponent() {
return (
<div css={someComponentCss}>
<h1 css={titleCss}>Some heading</h1>
<p>
Click <a href="#">here</a> to see something nice.
</p>
</div>
);
}
export default SomeComponent;
import { css } from '@emotion/react';
const titleCss = fontSize => css`
font-size: ${fontSize}rem;
`;
function SomeComponent({ fontSize }) {
return (
<div>
<h1 css={titleCss(fontSize)}>Some heading</h1>
</div>
);
}
export default SomeComponent;
- Conditional CSS blocks should be wrapped with
css
to support intellisense
import { css } from '@emotion/react';
const buttonCss = isActive => css`
/* Conditional ternary value */
background-color: ${isActive ? 'blue' : 'white'};
/* Conditional value, if falsy, the property will not be set at all */
z-index: ${isActive && 100};
/* Conditional block of CSS */
${isActive &&
css`
transform: scale(2);
color: white;
`}
`;
function SomeComponent({ isActive }) {
return (
<div>
<button css={buttonCss(isActive)}>Do Something</button>
</div>
);
}
export default SomeComponent;
- Follow the suggestions in Avoiding CSS overrides in responsive components.
- The default CSS that we write (outside any media query) targets all viewports.
- Any property that would be overwritten on tablet viewport should be inside
mobileOnly
. - The media query helpers are defined in
styles/utils.ts
.
import { mobileOnly, tabletUp, desktopUp } from 'styles/utils';
const compNameCss = css`
display: flex;
${mobileOnly} {
flex-direction: column;
}
${tabletUp} {
flex-direction: row;
padding: 20px;
}
${desktopUp} {
padding: 40px;
}
`;
- Don't use MUI styling (JSS) at all.
- Override styles using the relevant selectors for each component.
- Each MUI component documents the available class names in the doc (e.g. MUI Alert API)
- Some components are just wrapper over other components (e.g. MUI TextField)
// This is a wrapper component with the same name as the MUI component.
import { Alert as MuiAlert } from '@material-ui/lab';
import { css } from '@emotion/react';
import { colors } from 'styles/theme';
import { setOpacity } from 'styles/utils';
const muiAlertCss = css`
&.MuiAlert-root {
padding: 10px;
font-size: 1.8rem;
justify-content: center;
border-radius: 7px;
.MuiAlert-message {
padding: 0;
text-align: center;
}
/* Error */
&.MuiAlert-standardError {
color: ${colors.dangerRed};
background-color: ${setOpacity(colors.dangerRed, 0.1)};
}
/* Success */
&.MuiAlert-standardSuccess {
color: ${colors.successGreenText};
background-color: ${setOpacity(colors.successGreenBg, 0.1)};
}
}
`;
function Alert({ children, ...restProps }) {
return (
<MuiAlert css={muiAlertCss} {...restProps}>
{children}
</MuiAlert>
);
}
In case we need to add a class name in CSS but not necessarily pass it directly
to a component via css
, we can use Emotion's Class Names.
The css
from ClassNames
just adds the given styles into a <style>
tag in
the HTML and returns the class name string.
This is useful, for example, with MUI classes
, which adds class names on top
of inner elements.
Usually we won't need it, but some components like Autocomplete
have inner
elements that are rendered in a React Portal outside the component's tree, which
means that are our styling convention above won't work for those elements.
// This is a wrapper component with the same name as the MUI component.
import { Autocomplete as MuiAutocomplete } from '@material-ui/lab';
import { css, ClassNames } from '@emotion/react';
const muiAutocompleteCss = css`
.MuiTouchRipple-root {
display: none;
}
.MuiButtonBase-root:hover {
background-color: transparent;
}
`;
/**
* We use `classes` since the Autocomplete Popper is opened in a React Portal,
* thus can't be reached with our regular CSS.
*/
const classesCss = css => ({
paper: css`
&.MuiPaper-root {
font-size: 1.8rem;
font-weight: 300;
margin: 2px 0;
}
.MuiAutocomplete-listbox {
padding: 10px 0;
}
.MuiAutocomplete-option {
padding: 7px 18px;
}
`,
});
function Autocomplete(props) {
return (
<ClassNames>
{({ css }) => (
<MuiAutocomplete
css={muiAutocompleteCss}
classes={classesCss(css)}
{...props}
/>
)}
</ClassNames>
);
}
Why not Emotion Styled?
It's a good question...
Well, the quick answer would be "I tried Styled and didn't like it" 😀.
But I could think of some reasons:
css
I build styling blocks and apply them where I want and can reuse the same block for different elements.With Styled I need to think of the element and composition when I'm thinking about styling.
CheckoutProgress
that I want to style,CheckoutProgressStyled
?It's technically similar with the
css
prop, but I find it easier to name, e.g.checkoutProgressCss
.css
prop more flexible, either passing a reference to a css block or calling a function and passing what I need.e.g. I can pass something to the styling block that is not meant to be part of the props.
Especially with TypeScript passing extra props just for the styling with Styled adds more work (I think).
I suppose there are downsides to the
css
prop:className
).className
from the parent, it's important to forwardclassName
after the internalcss
prop usage, otherwise thecss
one will override the parent'sclassName
.Why not styled-components?
Emotion can be used just like styled-components, see:
https://emotion.sh/docs/styled
MUI v5 will most likely use Emotion, and they compared a bunch of solutions:
mui/material-ui#22342
I see no benefit of using styled-components other than it's more popular.