Thursday, March 28th 2019
Styling Next.js with Styled JSX
Posted byStyled JSX is a CSS-in-JS library that allows you to write encapsulated and scoped CSS to style your components. The styles you introduce for one component won't affect other components, allowing you to add, change and delete styles without worrying about unintended side effects.
Getting started
Next.js includes Styled JSX by default, so getting started is as simple as adding a <style jsx>
tag into an existing React element and writing CSS inside of it:
function Home() {
return (
<div className="container">
<h1>Hello Next.js</h1>
<p>Let's explore different ways to style Next.js apps</p>
<style jsx>{`
.container {
margin: 50px;
}
p {
color: blue;
}
`}</style>
</div>
);
}
export default Home;
In this example, we're including styles for the component's container element and a paragraph. Even though we are using generic selectors, the styles don't affect elements with the container
class name or <p>
tags in other components. This is because Styled JSX ensures the styles are scoped to this component only (by applying additional unique class names to styled elements).
By adding just a single jsx
attribute to a <style>
element, you can write standard CSS that gets auto prefixed and automatically scoped to the component. <style jsx>
elements should be placed inside the root element of the component.
Adding global styles
Most projects need some global styles to style the body element or provide css resets. Styled JSX allows us to add global styles using <style jsx global>
. For example:
function Home() {
return (
<div className="container">
<h1>Hello Next.js</h1>
<p>Let's explore different ways to style Next.js apps</p>
<style jsx>{`
.container {
margin: 50px;
}
p {
color: blue;
}
`}</style>
<style jsx global>{`
p {
font-size: 20px;
}
`}</style>
</div>
);
}
export default Home;
This applies a 20px font-size to all <p>
tags in this specific page.
To apply global styles to all pages in our app, a good approach is to first create a layout component with the global styles, then wrap all pages with it.
Using a layout component provides the flexibility to apply a certain set of styles to some pages while still allowing a different style for others:
function Layout(props) {
return (
<div className="page-layout">
{props.children}
<style jsx global>{`
body {
margin: 0;
padding: 0;
font-size: 18px;
font-weight: 400;
line-height: 1.8;
color: #333;
font-family: sans-serif;
}
h1 {
font-weight: 700;
}
p {
margin-bottom: 10px;
}
`}</style>
</div>
);
}
export default Layout;
In Next.js, we can load the layout once for all pages by creating a custom App
component within pages/_app.js
, importing the Layout
component, and then adding it to the render method as follows:
import React from 'react';
import App, { Container } from 'next/app';
import Layout from '../components/Layout';
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Layout>
<Component {...pageProps} />
</Layout>
</Container>
);
}
}
export default MyApp;
Writing styles in external files
We can also write styles in external files outside of the component.
For example, we can move our global styles from the Layout
component into a separate file as follows:
import css from 'styled-jsx/css';
export default css.global`
body {
margin: 0;
padding: 0;
font-size: 18px;
font-weight: 400;
line-height: 1.8;
color: #333;
font-family: sans-serif;
}
h1 {
font-weight: 700;
}
p {
margin-bottom: 10px;
}
`;
We can then import the styles back into the Layout
component:
import globalStyles from '../styles/global.js';
function Layout(props) {
return (
<div className="page-layout">
{props.children}
<style jsx global>
{globalStyles}
</style>
</div>
);
}
export default Layout;
One-off global selectors
The styles that we add to a component using <style jsx>
affect only the elements inside that component, but not child components.
At times, we may need to override a certain style of a child component. To do this, Styled JSX provides :global()
, giving access to one-off global selectors.
For example, let's say we have a <Widget>
component that contains a button with the class name btn
. If we want to change the colors of this button only when the widget is imported on the homepage, we can do so like this:
import Widget from '../components/Widget';
function Home() {
return (
<div className="container">
<h1>Hello Next.js</h1>
<Widget />
<style jsx>{`
.container {
margin: 50px;
}
.container :global(.btn) {
background: #000;
color: #fff;
}
`}</style>
</div>
);
}
export default Home;
Dynamic styles
Compared to other solutions, being able to adapt the styling of a component based on its props is a big advantage of CSS-in-JS
libraries.
With Styled JSX we can do so like this:
function Alert(props) {
return (
<div className="alert">
{props.children}
<style jsx>{`
.alert {
display: inline-block;
padding: 20px;
border-radius: 8px;
}
`}</style>
<style jsx>{`
.alert {
background: ${props.type == 'warning' ? '#fff3cd' : '#eee'};
}
`}</style>
</div>
);
}
export default Alert;
If the Alert
component is passed a type
prop with a warning
value like:
<Alert type="warning">This is an important message</Alert>
the component will have an orange background. Without specifying the type prop, the background would fallback to the default grey color.
Note that we placed the dynamic style into a separate <style jsx>
tag. This isn't required, but it's recommended to split up static and dynamic styles so that only the dynamic parts are recomputed when props change.
An alternate approach to adapting styles based on props is applying different class names based on the prop value as shown below:
function Alert(props) {
return (
<div className={props.type == 'warning' ? 'alert warning' : 'alert'}>
{props.children}
<style jsx>{`
.alert {
display: inline-block;
padding: 20px;
border-radius: 8px;
background: #eee;
}
.alert.warning {
background: #fff3cd;
}
`}</style>
</div>
);
}
export default Alert;
Creating a site theme
A theme can be a simple object where we include the most common variables we might need in our app:
const theme = {
fontFamily: {
sansSerif: '-apple-system, "Helvetica Neue", Arial, sans-serif',
mono: 'Menlo, Monaco, monospace',
},
colors: {
text: '#333',
background: '#fff',
link: '#1eaaf1',
linkHover: '#0d8ecf',
border: '#ddd',
warning: '#fff3cd',
success: '#d4edda',
},
};
export default theme;
We then import this theme file in our components and replace hardcoded values with variables:
import theme from '../styles/theme';
function Layout(props) {
return (
<div className="page-wrapper">
{props.children}
<style jsx global>{`
body {
background: ${theme.colors.background};
color: ${theme.colors.text};
font-family: ${theme.fontFamily.sansSerif};
}
`}</style>
<style jsx global>{`
body {
margin: 0;
padding: 0;
font-size: 18px;
font-weight: 400;
line-height: 1.8;
}
h1 {
font-weight: 700;
}
p {
margin-bottom: 10px;
}
`}</style>
</div>
);
}
export default Layout;
import theme from '../styles/theme';
function Alert(props) {
return (
<div className="alert">
{props.children}
<style jsx>{`
.alert {
display: inline-block;
padding: 20px;
border-radius: 8px;
}
`}</style>
<style jsx>{`
.alert {
background: ${props.type == 'warning'
? theme.colors.warning
: theme.colors.light};
}
`}</style>
</div>
);
}
export default Alert;
In this blog post, we covered how to get started with Styled JSX. To learn more about additional features, be sure to check it out on GitHub.