React applications are being composed of small components that can be used individually and in best case scenario can be reused across multiple applications. But what about the CSS that they need for layouting elements inside them?
Often you end up inventing a system for it, something like that: you group CSS rules by a class name for each component and everything that is specific to a component goes in there. It's a start but it's not perfect. Soon you start renaming stuff or you want to apply styles from a global perspective.
Coming from the Vue.js world, I especially liked the approach of Single File Components - everything that belongs to that component goes into one file, CSS, HTML and JavaScript or TypeScript and I wanted to have this in React too, so I took of into the world of CSS-in-JS.
CSS Modules
I came to React, looking for Single File Components all over the place and as it turns out, it's not that easy π What I did find though, is CSS Modules π₯³
It works like this: you import the CSS as a JavaScript module , which have been mapped from your CSS class names and assign those as className
properties in the JSX. I used the npm package typescript-plugin-css-modules
for this. This is how a Component styled with it looks like:
import styles from "./foo.css";
const FooComponent = () => {
return <div className={styles.myClassName}>Hello, World!</div>;
};
I still wasn't satisfied with this approach as there were still two files to edit when the component needs to be modified. Then I learned about Emotion in Jason Lengstorf's Introduction to Gatsby course on Frontend Masters and it was exactly what I was looking for. I was intruiged π
Emotion to the rescue
To style React components with emotion, there are several options to choose from depending on your preferences and what you want to achieve. The one I like most as a starting point, is using the css
-template string helper that lets you write CSS like you would in a CSS file. This is a sample component using it to set a width
, height
and background-color
:
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import React from "react";
function Card() {
return (
<div
css={css`
width: 100px;
height: 100px;
background-color: red;
`}
>
using the css template string helper
</div>
);
}
export default Card;
Really simple, huh? The first line /** @jsxImportSource @emotion/react */
tells the TypeScript compiler, how to resolve the css helper and it took me quite a while to figure it out!
But it can get quite messy when the component grows and contains more tags than just this one div. For this occasion, you can refactor the component and use styled components like this:
import styled from "@emotion/styled";
const ListItem = styled("li")`
font-weight: bold;
`;
interface ListProps {
items: Array<string>;
}
function List({ items }: ListProps) {
return (
<ul>
{items.map((item) => (
<ListItem key={item}>{item}</ListItem>
))}
</ul>
);
}
export default List;
As you can see, ListItem
uses the styled
function to create a styled component that only adds CSS to a li
element and automatically renders its children in it.
Now I got to the point where I was satisfied. At least until I realized that I wanted to have kind of like a theming, where I would store colors, definitions of borders and such things that I would need over and over again in a central location. Emotion provides a Theming API, why not try that?
Theming
To start with the theme, I implemented a new class for it and created a new instance of it:
class Theme {
readonly primaryColor: string = "green";
}
const theme = new Theme();
Theming then works like this: you provide the Theme to your components using a <ThemeProvider>
and access the theme in the actual component using the useTheme
hook provided by @emotion/react
. Here's my App, that does exactly that:
import { ThemeProvider } from "@emotion/react";
function App() {
return (
<div>
<ThemeProvider theme={theme}>
<MyComponentWithTheme />
</ThemeProvider>
</div>
);
}
And here is MyComponentWithTheme
, which uses both the Theme and the css
template string helper:
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { useTheme } from "@emotion/react";
import { Theme } from "../App";
function ComponentWithTheme() {
const theme = useTheme() as Theme;
return (
<div
css={css`
width: 100px;
height: 100px;
background: ${theme.primaryColor};
`}
>
Component using the Theme provided in App
</div>
);
}
export default ComponentWithTheme;
Using that, I found a way to write the CSS I need for my components directly in the components. I still need to figure out what parts of the style sheets go into a component or into a global style sheet, but it's a start.
Of course emotion does a lot more than that (like adding vendor prefixes and stuff) but I am still learning about it and find it very interesting and fun. I am looking forward for your tips and tricks around emotion and CSS-in-JS in common!
Top comments (3)
In a bigger project over time you don't want to have your CSS mixed with JavaScript. There are two big reasons for this:
Maintenance and code readability suffer: the components become too big and verbose. When dealing with styles you want to focus on styles, when dealing with code you want to only see the relevant code. You could solve it by making style-only components and logic-only components, but if working with a team it is hard to keep the discipline.
Performance becomes an issue. And it isn't only the executional performance, but code size and duplication. On client browser with CSS-in-JS you have the actual CSS, you have the JS building CSS, and you have the code that converts the JS interpretation to CSS.
CSS Modules don't have either of the issues, because CSS remains in it's own file, only relevant CSS gets bundled, and there is no runtime interpretation. Themes: use CSS variables.
Thanks for your input! I am still a learner in frontend tech and haven't worked on large projects (yet). So would you advise to start with CSS Modules or go for CSS-in-JS only when the project won't become large anyways?
CSS-in-JS only if it won't become large. On large projects it is a constant struggle of fighting to have less JavaScript executing on client's browser, because you want to have a site that is fast to serve and fast to execute. Good metrics on these are good for both user experience and search engine ranking. And this is why building everything through JS is a bad idea.