Methods for styling your React app
If you're new to React, you might be wondering why there are so many different tutorials that teach different ways to style your React app. The truth is, we're all still figuring out the best way to do things.
Styles in React were more-or-less worked out in this order:
- Global CSS
- CSS Modules
- CSS in JS (styled-components, Emotion, etc)
- Utility-first CSS
- Styled System
- Statically extracted CSS in JS
These days, I recommend starting with CSS in JS. If you'd like to know why, read on.
Quick note
When I say styling, I mean writing your CSS styles more-or-less from scratch. If you're looking for pre-built components, I wrote a guide to commonly used React component libraries.
Global CSS
Global CSS is likely the way you're used to styling webpages. You have a giant styles.css
file, and try to write BEM or SMACSS names for all of your classes. Alternatively, you have a ton of tiny files, and don't always know where each class lives.
We as frontend developers quickly realised that global CSS doesn't really scale. The more teams you have editing a single file, the more likely you are to have CSS that doesn't do anything (people become too afraid to delete anything in case it breaks).
If you still want to use Global CSS in your React app, all you need to do is import the CSS file at the top level of your React app (assuming you've configured webpack to do so, or are using create-react-app).
//App.jsimport './styles.css';import React from 'react';
const App = () => { return <div className="some-class">some other stuff in here</div>;};
CSS Modules
CSS Modules look a lot like Global CSS, in the sense that you're importing a CSS file into your React component, but under the hood it's quite different.
A lot of the problems we used to have with Global CSS are gone with CSS Modules.
Your CSS looks like this:
/* style.css */.makeItGreen { color: green;}
and your React Component looks like this:
import React from 'react';import styles from './style.css';
const MyComponent = () => { return <div className={styles.makeItGreen}>Green!</div>;};
The key difference here is that only files that import style.css
will be able to access the class names that it defines, and the class names that get generated during the build process will be unique.
No more conflicts, no more "too afraid to delete things in case it breaks", just locally scoped CSS. You can also set-up SCSS/LESS support, if you need it.
The really cool thing about this is that you can start to play around with JavaScript to change a component's styles.
import React from 'react';import styles from './style.css';
const MyComponent = (props) => { const myStyle = props.color === 'RED' ? styles.makeItRed : styles.makeItGreen; return <div className={myStyle}>{props.color}!</div>;};
Although, that starts to get a bit messy if you're using several props to change the style and behaviour of your components. What if your styles could just be components?
CSS in JS
That's where CSS in JS comes in.
Libraries like styled-components and Emotion make it possible to wrap components (including divs, spans, <p>
tags, <a>
tags) with styles, and use them as React components.
The best part is, you can use all of the standard CSS features you're used to, such as media queries, and :hover
and :focus
selectors.
Our example from above now becomes:
import React from 'react';import styled from '@emotion/styled';// OR import styled from 'styled-components'
const StyledGreenThing = styled.div` color: ${(props) => (props.color === 'RED' ? 'red' : 'green')};`;
const MyComponent = (props) => { return ( <StyledGreenThing color={props.color}>{props.color}!</StyledGreenThing> );};
As of 2020, Emotion and styled-components are evenly matched performance-wise. Contributors to styled-components worked hard to bring their performance up to Emotion's level, so deciding which one to use isn't as much of a big deal any more (even if people argue endlessly about them as though they're sports teams).
Emotion does provide some extra options for styling, such as the css prop, while styled-components tries to keep a single, standard way of doing things via the styled
API.
Utility-first CSS
A guide to styling React apps wouldn't be complete without mentioning utility-first CSS frameworks such as Tailwind. You don't actually need React to use utility-first CSS, but in my opinion, it makes for a better developer experience when you add React and CSS in JS.
In short, Tailwind lets you style your components one class at a time. Here's what it looks like:
<div className="md:flex bg-white rounded-lg p-6"> <img className="h-16 w-16 md:h-24 md:w-24 rounded-full mx-auto md:mx-0 md:mr-6" src="avatar.jpg" /> <div className="text-center md:text-left"> <h2 className="text-lg">Erin Lindford</h2> <div className="text-purple-500">Product Engineer</div> <div className="text-gray-600">erinlindford@example.com</div> <div className="text-gray-600">(555) 765-4321</div> </div></div>
Which creates a component that looks like this:
You might be thinking that it's not a particularly re-usable solution, however it's possible to use Tailwind class names with your favourite CSS in JS library using twin.
You can then have styled Tailwind components:
import tw, { styled } from 'twin.macro';
const Input = styled.input` color: purple; ${tw`border rounded`}`;
export const MyStyledInput = () => { return <Input />;};
Styled System
Styled System takes the styled
API supplied by styled-components or Emotion, and adds utilities as props, rather than class names.
The Styled System approach is particularly powerful when it comes to theming/white labelling, as changing the entire appearance of your app can be done by replacing the theme.js
file you provide.
Your components end up looking like this:
import styled from '@emotion/styled';import { typography, space, color } from 'styled-system';
const Box = styled('div')(typography, space, color);
const UsedBox = () => { return ( <Box fontSize={4} fontWeight="bold" p={3} mb={[4, 5]} color="white" bg="primary" > Hello </Box> );};
Statically extracted CSS in JS
The trouble with CSS in JS is that it takes JavaScript to load your CSS. This slows things down big time, so people started looking for ways to extract the CSS from CSS-in-JS during build time.
There are a few libraries that can do this:
Compiled and linaria let you use the styled
API that you know and love, while giving you the performance benefit of not having CSS in your bundle.
(Shameless plug for the useEffect book I wrote below)
Tired of infinite re-renders when using useEffect?
A few years ago when I worked at Atlassian, a useEffect bug I wrote took down part of Jira for roughly one hour.
Knowing thousands of customers can't work because of a bug you wrote is a terrible feeling. To save others from making the same mistakes, I wrote a single resource that answers all of your questions about useEffect, after teaching it here on my blog for the last couple of years. It's packed with examples to get you confident writing and refactoring your useEffect code.
In a single afternoon, you'll learn how to fetch data with useEffect, how to use the dependency array, even how to prevent infinite re-renders with useCallback.