Skip to main content

Next.js Emotion

Introduction

Emotion is a popular CSS-in-JS library that enables you to write CSS styles with JavaScript. When combined with Next.js, it provides a powerful way to create component-based styles with dynamic capabilities. Emotion offers two main APIs: a CSS prop and styled components. This guide will help you understand how to integrate and use Emotion in your Next.js applications effectively.

What is Emotion?

Emotion is a performant and flexible CSS-in-JS library that:

  • Allows for component-based styling
  • Provides automatic critical CSS extraction
  • Enables dynamic styling based on props
  • Supports theming
  • Has a tiny footprint and excellent performance

Setting Up Emotion in Next.js

Installation

First, install the required Emotion packages:

bash
npm install @emotion/react @emotion/styled @emotion/babel-plugin
# or
yarn add @emotion/react @emotion/styled @emotion/babel-plugin

Configuration

To configure Emotion with Next.js, you need to update your Next.js configuration. Create or modify your next.config.js file:

javascript
// next.config.js
module.exports = {
compiler: {
emotion: true,
},
};

For Next.js versions before 12.2, you'll need to customize the Babel configuration:

javascript
// .babelrc
{
"presets": [
[
"next/babel",
{
"preset-react": {
"runtime": "automatic",
"importSource": "@emotion/react"
}
}
]
],
"plugins": ["@emotion/babel-plugin"]
}

Using Emotion in Next.js

The CSS Prop Approach

The CSS prop approach allows you to directly write styles in your JSX:

jsx
import { css } from '@emotion/react';

function Button({ primary, children }) {
return (
<button
css={css`
background-color: ${primary ? '#0070f3' : 'white'};
color: ${primary ? 'white' : '#0070f3'};
border: 1px solid #0070f3;
border-radius: 4px;
padding: 8px 16px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;

&:hover {
opacity: 0.8;
}
`}
>
{children}
</button>
);
}

export default function HomePage() {
return (
<div>
<Button primary>Primary Button</Button>
<Button>Secondary Button</Button>
</div>
);
}

In this example, the button's style changes based on the primary prop, demonstrating Emotion's dynamic styling capabilities.

The Styled Components Approach

Emotion also offers a styled components API similar to styled-components:

jsx
import styled from '@emotion/styled';

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

&:hover {
opacity: 0.8;
}
`;

export default function HomePage() {
return (
<div>
<Button primary>Primary Button</Button>
<Button>Secondary Button</Button>
</div>
);
}

This approach creates reusable styled components that accept props for dynamic styling.

Advanced Emotion Features

Global Styles

You can define global styles using the Global component from Emotion:

jsx
import { Global, css } from '@emotion/react';

function MyApp({ Component, pageProps }) {
return (
<>
<Global
styles={css`
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

* {
box-sizing: border-box;
}
`}
/>
<Component {...pageProps} />
</>
);
}

export default MyApp;

Theming

Emotion supports theming through the ThemeProvider component:

jsx
import { ThemeProvider } from '@emotion/react';
import styled from '@emotion/styled';

// Define your theme
const theme = {
colors: {
primary: '#0070f3',
secondary: '#ff0080',
background: '#f5f5f5',
text: '#333333',
},
fonts: {
body: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif',
heading: 'Georgia, serif',
},
};

// Create themed components
const Box = styled.div`
background-color: ${props => props.theme.colors.background};
color: ${props => props.theme.colors.text};
padding: 20px;
font-family: ${props => props.theme.fonts.body};
`;

const Heading = styled.h1`
font-family: ${props => props.theme.fonts.heading};
color: ${props => props.theme.colors.primary};
`;

// Apply the theme to your app
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
);
}

export default MyApp;

Then you can use your themed components:

jsx
export default function HomePage() {
return (
<Box>
<Heading>Welcome to My Themed App</Heading>
<p>This text uses theme colors and fonts.</p>
</Box>
);
}

Composition and Object Styles

Emotion allows you to compose styles and use JavaScript object notation:

jsx
import { css } from '@emotion/react';
import styled from '@emotion/styled';

const baseButtonStyles = css`
border-radius: 4px;
padding: 8px 16px;
font-size: 16px;
cursor: pointer;
`;

const primaryButtonStyles = css`
background-color: #0070f3;
color: white;
`;

function Button({ primary, children }) {
return (
<button
css={[
baseButtonStyles,
primary && primaryButtonStyles,
css`
&:hover {
transform: translateY(-1px);
}
`
]}
>
{children}
</button>
);
}

// Object styles
const Card = styled.div({
backgroundColor: 'white',
borderRadius: 8,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
padding: 16,
margin: 8,

'&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
}
});

Server-Side Rendering with Emotion

Emotion is designed to work well with Next.js server-side rendering. With the configuration we set up earlier, Emotion will automatically extract and inject critical CSS during server-side rendering.

Real-World Example: Building a Card Component

Let's build a reusable card component that adapts based on different props:

jsx
import React from 'react';
import styled from '@emotion/styled';
import { css } from '@emotion/react';

// Base card styles
const CardContainer = styled.div`
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
margin: 16px;
width: ${props => props.wide ? '100%' : '300px'};

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

const CardImage = styled.div`
width: 100%;
height: 200px;
background-image: url(${props => props.src});
background-size: cover;
background-position: center;
`;

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

const CardTitle = styled.h3`
margin-top: 0;
margin-bottom: 8px;
color: #333;
font-size: 18px;
font-weight: 600;
`;

const CardDescription = styled.p`
color: #666;
font-size: 14px;
line-height: 1.5;
margin: 0;
`;

const CardFooter = styled.div`
display: flex;
justify-content: space-between;
padding: 16px;
border-top: 1px solid #eee;
`;

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

&:hover {
opacity: 0.8;
}
`;

// The Card Component
function Card({ title, description, imageSrc, wide, primaryAction, secondaryAction }) {
return (
<CardContainer wide={wide}>
{imageSrc && <CardImage src={imageSrc} />}

<CardContent>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardContent>

<CardFooter>
{secondaryAction && (
<Button onClick={secondaryAction.onClick}>
{secondaryAction.label}
</Button>
)}
{primaryAction && (
<Button primary onClick={primaryAction.onClick}>
{primaryAction.label}
</Button>
)}
</CardFooter>
</CardContainer>
);
}

// Usage example
export default function ProductPage() {
return (
<div>
<h1>Featured Products</h1>
<div css={css`
display: flex;
flex-wrap: wrap;
justify-content: center;
`}>
<Card
title="Wireless Headphones"
description="Premium noise cancelling wireless headphones with crystal clear sound."
imageSrc="https://example.com/headphones.jpg"
primaryAction={{
label: "Buy Now",
onClick: () => alert("Purchase initiated!")
}}
secondaryAction={{
label: "Details",
onClick: () => alert("Showing details...")
}}
/>

<Card
title="Smart Watch"
description="Track your fitness goals and stay connected with this stylish smart watch."
imageSrc="https://example.com/smartwatch.jpg"
wide
primaryAction={{
label: "Add to Cart",
onClick: () => alert("Added to cart!")
}}
/>
</div>
</div>
);
}

This example demonstrates a flexible card component that can be configured through props to display different content and layouts.

Performance Considerations

While Emotion provides excellent developer experience, here are some tips to ensure optimal performance:

  1. Use the babel plugin: This ensures styles are precompiled for better performance.
  2. Memoize dynamic styles: If you're generating styles based on props, memoize them to prevent unnecessary recalculations.
  3. Use the object styles syntax for complex styles with many conditions.
  4. Extract reusable styles to avoid creating new style objects on each render.
jsx
import { css } from '@emotion/react';
import { useMemo } from 'react';

function DynamicComponent({ color, size, disabled }) {
// Memoize dynamic styles
const dynamicStyles = useMemo(() => css`
color: ${color};
font-size: ${size}px;
opacity: ${disabled ? 0.5 : 1};
`, [color, size, disabled]);

return <div css={dynamicStyles}>Dynamic Content</div>;
}

Summary

Emotion is a powerful CSS-in-JS library that integrates seamlessly with Next.js, providing you with a flexible and efficient way to style your components. In this guide, we've covered:

  • How to set up Emotion with Next.js
  • The CSS prop approach for inline styling
  • The styled components API for creating reusable styled components
  • Advanced features like global styles, theming, and composition
  • Server-side rendering with Emotion
  • A practical example of building a card component
  • Performance considerations

Emotion allows you to write maintainable, dynamic styles that leverage the full power of JavaScript while still producing optimized CSS for your Next.js applications.

Additional Resources

Exercises

  1. Create a theme for your Next.js application using Emotion's ThemeProvider.
  2. Build a responsive navigation bar component that changes styles based on screen size using Emotion's media queries.
  3. Implement a dark mode toggle using Emotion's theming capabilities.
  4. Create a button component with multiple variants (primary, secondary, danger, etc.) using Emotion's composition features.
  5. Refactor an existing CSS or SCSS project to use Emotion and discuss the benefits and challenges of the migration.


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