Skip to main content

CSS-in-JS in Next.js

Introduction

CSS-in-JS is a styling approach where you write CSS directly in your JavaScript code. This paradigm offers component-scoped styles that can respond to component props, making your styling more dynamic and tightly coupled with your components. In Next.js applications, CSS-in-JS libraries provide an alternative to traditional CSS, CSS Modules, or Sass.

In this guide, we'll explore how to use popular CSS-in-JS libraries with Next.js, understand their benefits and drawbacks, and see practical examples of implementing them in your projects.

Why Use CSS-in-JS in Next.js?

CSS-in-JS offers several advantages for Next.js applications:

  1. Component-scoped styles: Styles are isolated to specific components, preventing class name collisions
  2. Dynamic styling: Style properties can change based on component props or state
  3. No need for class naming conventions: Reduces the mental overhead of naming CSS classes
  4. Co-location: Keeps styles close to the components they affect
  5. JavaScript features in CSS: Use variables, functions, and other JavaScript features in your styles

1. styled-components

styled-components is one of the most popular CSS-in-JS libraries, allowing you to write actual CSS in your JavaScript files.

2. Emotion

Emotion is a flexible CSS-in-JS library that lets you write styles with JavaScript, similar to styled-components but with some different features and optimizations.

Setting Up CSS-in-JS in Next.js

Let's see how to set up and use these libraries in a Next.js project.

Setting up styled-components

  1. First, install styled-components:
bash
npm install styled-components
# or
yarn add styled-components
  1. To ensure server-side rendering works correctly with styled-components, you need to create a custom _document.js file:
jsx
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;

try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});

const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}

render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

Setting up Emotion

  1. Install Emotion:
bash
npm install @emotion/react @emotion/styled
# or
yarn add @emotion/react @emotion/styled
  1. For Next.js 12+, Emotion works out of the box without additional configuration for server-side rendering.

Using styled-components in Next.js

Let's create a simple Button component using styled-components:

jsx
// components/Button.js
import styled from 'styled-components';

const Button = styled.button`
background-color: ${props => props.primary ? '#0070f3' : 'white'};
color: ${props => props.primary ? 'white' : '#0070f3'};
font-size: 16px;
padding: 10px 20px;
border-radius: 4px;
border: 1px solid #0070f3;
cursor: pointer;
transition: all 0.2s ease;

&:hover {
background-color: ${props => props.primary ? '#005cc5' : '#f8f9fa'};
}

&:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(0, 112, 243, 0.3);
}
`;

export default Button;

Using this Button component in a page:

jsx
// pages/index.js
import Button from '../components/Button';

export default function Home() {
return (
<div>
<h1>Welcome to My App</h1>
<Button primary>Primary Button</Button>
<Button>Secondary Button</Button>
</div>
);
}

Output

When rendered, the primary button will have a blue background with white text, while the secondary button will have a white background with blue text. Both buttons will have hover and focus states with smooth transitions.

Using Emotion in Next.js

Let's create the same Button component using Emotion:

jsx
// components/Button.js
import styled from '@emotion/styled';

const Button = styled.button`
background-color: ${props => props.primary ? '#0070f3' : 'white'};
color: ${props => props.primary ? 'white' : '#0070f3'};
font-size: 16px;
padding: 10px 20px;
border-radius: 4px;
border: 1px solid #0070f3;
cursor: pointer;
transition: all 0.2s ease;

&:hover {
background-color: ${props => props.primary ? '#005cc5' : '#f8f9fa'};
}

&:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(0, 112, 243, 0.3);
}
`;

export default Button;

The usage in a page is the same as with styled-components:

jsx
// pages/index.js
import Button from '../components/Button';

export default function Home() {
return (
<div>
<h1>Welcome to My App</h1>
<Button primary>Primary Button</Button>
<Button>Secondary Button</Button>
</div>
);
}

Advanced CSS-in-JS Features

1. Theming

Both styled-components and Emotion support themes. Here's how to set up a theme with styled-components:

jsx
// pages/_app.js
import { ThemeProvider } from 'styled-components';
import '../styles/globals.css';

const theme = {
colors: {
primary: '#0070f3',
secondary: '#ff4081',
background: '#ffffff',
text: '#333333',
},
fonts: {
body: 'system-ui, sans-serif',
heading: 'Georgia, serif',
},
fontSizes: {
small: '14px',
medium: '16px',
large: '18px',
},
};

function MyApp({ Component, pageProps }) {
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
);
}

export default MyApp;

Using the theme in a component:

jsx
// components/ThemedButton.js
import styled from 'styled-components';

const ThemedButton = styled.button`
background-color: ${props => props.primary ? props.theme.colors.primary : 'white'};
color: ${props => props.primary ? 'white' : props.theme.colors.primary};
font-family: ${props => props.theme.fonts.body};
font-size: ${props => props.theme.fontSizes.medium};
padding: 10px 20px;
border-radius: 4px;
border: 1px solid ${props => props.theme.colors.primary};
cursor: pointer;
`;

export default ThemedButton;

2. Global Styles

You can define global styles using the createGlobalStyle function in styled-components:

jsx
// components/GlobalStyles.js
import { createGlobalStyle } from 'styled-components';

const GlobalStyles = createGlobalStyle`
body {
margin: 0;
padding: 0;
font-family: ${props => props.theme.fonts.body};
color: ${props => props.theme.colors.text};
background-color: ${props => props.theme.colors.background};
}

a {
color: ${props => props.theme.colors.primary};
text-decoration: none;
}
`;

export default GlobalStyles;

Then include it in your _app.js:

jsx
// pages/_app.js
import { ThemeProvider } from 'styled-components';
import GlobalStyles from '../components/GlobalStyles';

const theme = {
// Theme definition as before
};

function MyApp({ Component, pageProps }) {
return (
<ThemeProvider theme={theme}>
<GlobalStyles />
<Component {...pageProps} />
</ThemeProvider>
);
}

export default MyApp;

3. Extending Styles

You can extend existing styled components to create new variations:

jsx
// components/ExtendedButton.js
import styled from 'styled-components';
import Button from './Button';

const LargeButton = styled(Button)`
font-size: 20px;
padding: 12px 24px;
`;

const IconButton = styled(Button)`
display: flex;
align-items: center;
gap: 8px;

svg {
height: 16px;
width: 16px;
}
`;

export { LargeButton, IconButton };

Real-World Example: Creating a Card Component

Let's create a card component that can be used for displaying article previews:

jsx
// components/ArticleCard.js
import styled from 'styled-components';
import Link from 'next/link';
import Image from 'next/image';

const Card = styled.div`
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
background-color: white;

&:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
`;

const ImageContainer = styled.div`
position: relative;
height: 200px;
width: 100%;
`;

const Content = styled.div`
padding: 16px;
`;

const Title = styled.h3`
margin: 0 0 8px 0;
font-size: 18px;
color: #333;
`;

const Description = styled.p`
margin: 0 0 16px 0;
font-size: 14px;
color: #666;
`;

const Tag = styled.span`
display: inline-block;
background-color: #f0f0f0;
color: #666;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
margin-right: 8px;
`;

function ArticleCard({ article }) {
return (
<Card>
<ImageContainer>
<Image
src={article.coverImage}
alt={article.title}
layout="fill"
objectFit="cover"
/>
</ImageContainer>
<Content>
<Title>{article.title}</Title>
<Description>{article.description}</Description>
<div>
{article.tags.map(tag => (
<Tag key={tag}>{tag}</Tag>
))}
</div>
</Content>
</Card>
);
}

export default ArticleCard;

Using the ArticleCard component:

jsx
// pages/blog.js
import styled from 'styled-components';
import ArticleCard from '../components/ArticleCard';

const Grid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
padding: 24px;
`;

export default function Blog() {
const articles = [
{
id: 1,
title: 'Getting Started with Next.js',
description: 'Learn the basics of Next.js and how to build your first app.',
coverImage: '/images/nextjs-basics.jpg',
tags: ['Next.js', 'React', 'Tutorial'],
},
{
id: 2,
title: 'CSS-in-JS Explained',
description: 'A deep dive into styling your React components with CSS-in-JS libraries.',
coverImage: '/images/css-in-js.jpg',
tags: ['CSS-in-JS', 'Styling', 'React'],
},
// More articles...
];

return (
<div>
<h1>Blog</h1>
<Grid>
{articles.map(article => (
<ArticleCard key={article.id} article={article} />
))}
</Grid>
</div>
);
}

Performance Considerations

While CSS-in-JS offers many benefits, there are some performance considerations to keep in mind:

  1. Runtime overhead: CSS-in-JS libraries add some JavaScript overhead at runtime
  2. Bundle size: Including a CSS-in-JS library increases your bundle size
  3. Server-side rendering complexity: Without proper setup, CSS-in-JS can lead to the "flash of unstyled content"

To optimize performance:

  • Use a CSS-in-JS library that supports static extraction when possible
  • Consider using @emotion/css instead of @emotion/styled for smaller bundle sizes
  • Ensure proper server-side rendering setup
  • For highly performance-critical applications, consider CSS Modules as an alternative

Summary

CSS-in-JS in Next.js provides a powerful way to style components with the full power of JavaScript. We've covered:

  • Setting up popular CSS-in-JS libraries like styled-components and Emotion
  • Creating styled components with dynamic props
  • Using advanced features like theming and global styles
  • Building real-world components with CSS-in-JS
  • Understanding performance considerations

Both styled-components and Emotion offer similar APIs and features, so the choice between them often comes down to personal preference and specific project needs. CSS-in-JS works especially well for component libraries and applications with dynamic, prop-based styling needs.

Additional Resources

Exercises

  1. Create a responsive navigation bar using styled-components with mobile and desktop layouts
  2. Implement a dark/light theme toggle using CSS-in-JS with React context
  3. Build a form with styled components that validate input and show error states
  4. Create a card component that changes appearance based on a "featured" prop
  5. Try refactoring an existing component styled with CSS Modules to use CSS-in-JS

By mastering CSS-in-JS in Next.js, you'll have a powerful styling approach in your toolbox that integrates seamlessly with your component-based architecture.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)