Material-UI styled() API
October 24, 2020
I love me some styled-components
! Most of the projects I’ve worked with used @emotion
but I wanted to check out the Material-UI (MUI) styled()
API which apparently works exactly like styled components… well, “exactly” it’s totally accurate. Let’s take a look.
Choose Your Syntax
There are two syntaxes to choose from with styled-components
:
- backtick syntax
- object syntax
The backtick syntax is nice because it’s basically regular old CSS with the option to add some interpolated props if necessary. It’s also nice for those rare, rare moments that you might copy/paste CSS from some another source like StackOverflow (which I never do 🤞) although if using the object syntax the css-in-js VSCode extension converts the syntax from CSS to camelCase reasonably well.
The MUI styled()
API doesn’t yet support the backtick syntax (it’s coming in v5) but I prefer to use the object syntax anyway, mainly because…
- It looks cleaner (IMO)
- It’s consistent with the camelCase CSS used with the
style
attribute - It supports unitless numbers, so you don’t have to type
px
- Totally just my opinion, but backticks and interpolating
${props}
and${theme}
is a little too “backticky/quotey/curly brackety” for me (see below).
type Props = {
myProp: string,
};
// @emotion styled
const CustomButtonBacktick = styled.button`
color: ${({ myProp }) => `${myProp === "error" ? "#fff" : "#999"}`};
margin-left: 20px;
`;
// MUI styled
const CustomButtonObject = styled(Button)(({ myProp }: Props) => ({
color: myProp === "error" ? "red" : "blue",
marginLeft: 20,
}));
Not bad but there’s already a problem… in the above MUI example, the HTML renders with a myProp
attribute which is invalid for a <button />
element. One nice feature of styled-components
is it will filter out invalid HTML attributes for you. I’ll show how to handle this with MUI below.
Theming and Props
Accessing properties from the MUI theme is simple with the styled()
API. Just destructure the theme prop and access it in the component’s styles:
const MyDiv = styled("div")(({ theme }) => ({
color: theme.palette.secondary.main,
marginLeft: theme.spacing(3),
}));
Like I mentioned before, custom props need to be removed from the component, so they aren’t rendered to the HTML. This <CustomButton />
component will render an attribute named myProp
which obviously isn’t valid:
type Props = {
myProp: string,
};
// DON'T do this...
const CustomButton = styled(Button)(({ myProp }: Props) => ({
color: myProp === "error" ? "red" : "blue",
}));
<CustomButton myProp="error">Click</CustomButton>;
<!-- Rendered HTML -->
<button myprop="error">Click</button>;
To remove the custom prop, use destructuring to filter the props that are forwarded to the HTML element, then use those destructured props when creating the styles.
type Props = {
myProp: string,
};
const CustomButton = styled(({ myProp, ...validButtonProps }: Props) => (
<Button {...validButtonProps} />
))({
color: ({ myProp }: Props) => (myProp === "GOOD" ? "red" : "blue"),
});
<!-- Rendered HTML: -->
<button>Click</button>;
OK things are getting a little more verbose… and it gets even slightly more complicated when the component needs custom props and the MUI theme. One solution is to get the theme using MUI’s withTheme() HOC and use the same destructuring approach we did above:
type Props = {
myProp: string,
theme: Theme,
};
const CustomButton = withTheme(
styled(({ myProp, theme, ...validButtonProps }: Props) => (
<Button {...validButtonProps} />
))({
color: ({ myProp, theme: { palette } }: Props) =>
myProp === "error" ? palette.error.main : palette.primary.dark,
})
);
yee-ikes… that backtick syntax isn’t looking so complex anymore! Although I’m not covering it here, I think in this case it would make more sense to go with the useTheme()
hook and just toggle css values in the component
const CustomButton: React.FC<{ error: boolean }> = ({ error }) => {
const { palette } = useTheme();
const color = error ? palette.error.main : palette.primary.main;
return <button style={{ color }}>Hello</button>;
};
Media Queries and Breakpoints
The breakpoint API is a good way to add media media queries to a styled()
component. It looks a little funky at first but I actually like this approach because it uses the theme’s breakpoint widths so things stay consistent and it has some nice helper functions: up(), down(), only()
and between()
.
const MyDiv = styled("div")(({ theme }) => ({
color: theme.palette.secondary.dark,
[theme.breakpoints.up("lg")]: {
fontWeight: "bold",
},
[theme.breakpoints.only("sm")]: {
fontSize: "3rem",
},
[theme.breakpoints.between("lg", "xl")]: {
color: theme.palette.error.main,
fontSize: "5rem",
},
}));
Happy coding!