⚔️ Code Conqueror

🎩 Styling a Next.js Project

Nov 26, 2019

Next.js has a few built-in styling methods and many more are possible to integrate. We'll review the built in methods, how to integrate a custom method, and some ways to handle responsive styles.

CSS solutions in Next.js

There are a few built-in ways to style components in Next.js. The primary way is through styled-jsx which is quite a nice way to write css-in-js without some compromise of the component-based methods we'll discuss later. With styled-jsx you add a

style
tag inside each of your components that adds the styles either to the root or other components in the render-body. It will not cascade the styled down to children components (which has some pluses and minuses). It looks like:

const Card = ({ children }) => (
<div className="root">
{children}
<styled jsx>{`
.root {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
`}</styled>
</div>
);

In this example we target the root

div
inside the component with a class selector. Other kinds of css selectors work as well. One nice aspect of this is that all the styles live at the bottom of the component and you don't pollute the component with style names that are somewhat meaningless (eg
<BoxDiv>
or
<StyledBox>
etc.). Instead you can write:

const Profile = ({ name, bio, birthday }) => (
<div className="root">
<h1>{name}</h1>
<p>{bio}</p>
<strong>{birthday}</strong>
<styled jsx>{`
.root {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
h1 {
font-size: 24px;
}
p {
font-weight: bold;
}
strong {
color: grey;
}
`}</styled>
</div>
);

Styled-Components / Emotion

A more popular css-in-js solution comes with Emotion and Styled Components. When combining these with React you end up creating styled versions of your components via template literals like:

import styled from "styled-components"; // or @emotion/styled
const Card = styled.div`
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
`;

If you are simply styling a single component this method is quite compact, but it does often lead to creating a whole bunch of somewhat meaningless component names (when you just want a styled version). For that reason these days I tend to prefer styled-jsx, but both are great solutions.

The best reference for how to add these to Next.js can be found in the Next.js examples repository, with-styled-components and with-emotion. Those will be updated as Next.js possibly changes.

Looking at our

Profile
example:

import styled from "styled-components"; // or @emotion/styled
const ProfileContainer = styled.div`
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
`;
const Heading1 = styled.h1`
font-size: 24px;
`;
const P = styled.p`
font-weight: bold;
`;
const Strong = styled.strong`
color: grey;
`;
const Profile = ({ name, bio, birthday }) => (
<ProfileContainer>
<Heading1>{name}</Heading1>
<P>{bio}</P>
<Strong>{birthday}</Strong>
</ProfileContainer>
);

This does give us something for "free" which styled-jsx doesn't namely we can now use the sub-components elsewhere and start to develop our library of styled components. That being said with styled-jsx you could simply define components with

style
tags that scope to each one and then compose them in the same way we do above. Or if they are things that are meant to be global then you can include them at the root of the application (eg
_app.js
) with
style jsx global
.

Those are some of the pros / cons of these two approaches. I should also mention one more approach or family of approaches which I will call "prop" based css. Either through react's built-in

style
prop or Emotion's css prop. I find this approach to be very un-readable since it pollutes your render logic with styles. Eg in our example it would look like:

/** @jsx jsx */
import { jsx } from "@emotion/core";
const Profile = ({ name, bio, birthday }) => (
<div
css={{
boxShadow: "0 4px 8px 0 rgba(0,0,0,0.2)",
}}
>
<h1
css={{
fontSize: "24px",
}}
>
{name}
</h1>
<p
css={{
fontWeight: "bold",
}}
>
{bio}
</p>
<strong
css={{
color: "grey",
}}
>
{birthday}
</strong>
</div>
);

As I said, in this approach you pollute your render method with

css
props leading to a lot less easy to read code. Another downside is that you need another jsx pragma (eg the comment at the top:
/** @jsx jsx */
) and also you need to use Javascript objects to define css, which hurts readability / portability.

Responsive + Mobile-First Design

Now that we've introduced a few different ways to style a Next.js application. Let's take a look at one related topic which is how to get your application to be responsive to multiple widths.

Basically the idea is to show different styles depending on the size of the browser. A good introduction can be found on CSS Tricks.

An option that I've liked for a while to add responsive styles to a React application is styled-system which uses the emotion / styled-components css in combination with React props to determine the responsive styles. As an example, say that we want to have different padding values on our

div
for various screen sizes. We could write a bunch of media queries or better

import styled from "styled-components";
import { padding } from "styled-system";
const Box = styled.div`
${padding}
`;
// responsive padding
const Card = ({ children }) => <Box p={[1, 2, 3, 4]}>{children}</Box>;

And then you set the values for the padding

1, 2, 3, 4
in a
theme
object and supply it via React context.

The advantage here is that you get a super succinct way to style a component in a responsive way. The disadvantage is that we are back to using react-props api to add styles, otherwise polluting our application logic.

I think a pretty sweet project would be to see if we can develop a styled-jsx like api for writing the css but a styled-system like interface for dealing with responsive styles. On this site though we just used styled-jsx and media queries to accomplish a similar thing eg:

const Card = () => (
<div className="root">
{children}
<styled jsx>{`
.root {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
@media screen and (max-width: ${theme.breakpoints[0]}) {
.root {
padding: 12px;
}
}
`}</styled>
</div>
);

But we have to add a separate media query for every size (much more cumbersome than

p={[1, 2, 3, 4]}
.