Next.js Styled Components
Introduction
Styled Components is a popular CSS-in-JS library that allows you to write actual CSS code to style your components in JavaScript. When used with Next.js, it provides a powerful way to create dynamic, maintainable styles that are scoped to specific components. This approach eliminates class name bugs and makes your styling more intuitive by directly connecting it to your components.
In this lesson, we'll explore how to integrate Styled Components with Next.js, understand its benefits, and learn best practices through practical examples.
Why Use Styled Components?
Before diving into implementation, let's understand why Styled Components is a great choice for styling your Next.js applications:
- Component-based: Styles are tied directly to components
- Scoped styles: No class name collisions
- Dynamic styling: Easily adjust styles based on props
- Theming: Simplified theming across your application
- Automatic vendor prefixing: Write modern CSS without worrying about browser compatibility
Setting Up Styled Components in Next.js
Let's start by adding Styled Components to your Next.js project.
Step 1: Install Styled Components
First, we need to install the required packages:
npm install styled-components
npm install --save-dev @types/styled-components # if using TypeScript
Step 2: Configure Next.js for Server-Side Rendering
To make Styled Components work properly with Next.js's server-side rendering, we need to add some configuration.
Create a file called _document.js
in your pages
directory:
// 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>
);
}
}
This configuration ensures that styles are properly generated during server-side rendering, preventing the "flash of unstyled content" (FOUC) issue.
Creating Your First Styled Component
Now that everything is set up, let's create a simple styled component:
// components/StyledButton.js
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: #0070f3;
color: white;
font-size: 1rem;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
&:hover {
background-color: #0051a2;
}
`;
export default StyledButton;
You can then use this component in any page or component:
// pages/index.js
import StyledButton from '../components/StyledButton';
export default function Home() {
return (
<div>
<h1>Welcome to My Next.js App</h1>
<StyledButton>Click Me</StyledButton>
</div>
);
}
When rendered, this will display a blue button that darkens when hovered over, with all the styles scoped to just that button.
Dynamic Styling with Props
One of the most powerful features of Styled Components is the ability to make styles dynamic based on props:
// components/DynamicButton.js
import styled from 'styled-components';
const DynamicButton = styled.button`
background-color: ${props => props.primary ? '#0070f3' : 'white'};
color: ${props => props.primary ? 'white' : '#0070f3'};
font-size: 1rem;
padding: 0.5rem 1rem;
border: 2px solid #0070f3;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: ${props => props.primary ? '#0051a2' : '#f8f9fa'};
}
`;
export default DynamicButton;
This button can now be used in two different styles:
// Usage
<DynamicButton>Secondary Button</DynamicButton>
<DynamicButton primary>Primary Button</DynamicButton>
The first button will have a white background with blue text, while the second will have a blue background with white text.
Extending Styles
You can extend existing styled components with additional styles:
// components/ExtendedButton.js
import styled from 'styled-components';
import DynamicButton from './DynamicButton';
const ExtendedButton = styled(DynamicButton)`
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
`;
export default ExtendedButton;
This creates a new button component that inherits all styles from DynamicButton
but adds bold text, uppercase transformation, and letter spacing.
Creating a Global Style
For application-wide styles, Styled Components provides the createGlobalStyle
function:
// styles/GlobalStyles.js
import { createGlobalStyle } from 'styled-components';
const GlobalStyles = createGlobalStyle`
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
a {
color: #0070f3;
text-decoration: none;
}
`;
export default GlobalStyles;
Add the global styles to your app by including this component in your _app.js
:
// pages/_app.js
import GlobalStyles from '../styles/GlobalStyles';
function MyApp({ Component, pageProps }) {
return (
<>
<GlobalStyles />
<Component {...pageProps} />
</>
);
}
export default MyApp;
Theme Support
Styled Components includes a ThemeProvider that enables theming across your application:
// styles/theme.js
const theme = {
colors: {
primary: '#0070f3',
secondary: '#ff4081',
success: '#4caf50',
error: '#f44336',
background: '#f5f5f5',
text: '#333',
},
fonts: {
heading: 'Georgia, serif',
body: 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif',
},
breakpoints: {
mobile: '576px',
tablet: '768px',
laptop: '992px',
desktop: '1200px',
},
};
export default theme;
Apply the theme in your _app.js
:
// pages/_app.js
import { ThemeProvider } from 'styled-components';
import GlobalStyles from '../styles/GlobalStyles';
import theme from '../styles/theme';
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider theme={theme}>
<GlobalStyles />
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;
Now you can use the theme in your styled components:
// components/ThemedButton.js
import styled from 'styled-components';
const ThemedButton = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
font-family: ${props => props.theme.fonts.body};
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
@media (max-width: ${props => props.theme.breakpoints.mobile}) {
font-size: 0.8rem;
padding: 0.4rem 0.8rem;
}
`;
export default ThemedButton;
Real-World Example: Card Component
Let's create a practical example of a reusable card component with Styled Components:
// components/Card/styles.js
import styled from 'styled-components';
export const CardContainer = styled.div`
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
`;
export const CardImage = styled.img`
width: 100%;
height: 200px;
object-fit: cover;
`;
export const CardContent = styled.div`
padding: 1.5rem;
`;
export const CardTitle = styled.h2`
margin: 0 0 0.5rem;
color: ${props => props.theme.colors.text};
font-family: ${props => props.theme.fonts.heading};
`;
export const CardDescription = styled.p`
color: #666;
margin-bottom: 1rem;
`;
export const CardFooter = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-top: 1px solid #eee;
`;
export const CardButton = styled.button`
background-color: ${props => props.theme.colors.primary};
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
&:hover {
background-color: ${props => props.secondary
? props.theme.colors.secondary
: '#0051a2'};
}
`;
Now we can create the actual Card component:
// components/Card/index.js
import {
CardContainer,
CardImage,
CardContent,
CardTitle,
CardDescription,
CardFooter,
CardButton
} from './styles';
const Card = ({
title,
description,
imageUrl,
primaryButtonText,
secondaryButtonText,
onPrimaryClick,
onSecondaryClick
}) => {
return (
<CardContainer>
{imageUrl && <CardImage src={imageUrl} alt={title} />}
<CardContent>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardContent>
<CardFooter>
{primaryButtonText && (
<CardButton onClick={onPrimaryClick}>
{primaryButtonText}
</CardButton>
)}
{secondaryButtonText && (
<CardButton
secondary
onClick={onSecondaryClick}
>
{secondaryButtonText}
</CardButton>
)}
</CardFooter>
</CardContainer>
);
};
export default Card;
Using this component in a page:
// pages/products.js
import Card from '../components/Card';
export default function Products() {
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '2rem' }}>
<h1>Our Products</h1>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
gap: '2rem',
marginTop: '2rem'
}}>
<Card
title="Next.js Book"
description="Learn Next.js from beginner to advanced with this comprehensive guide."
imageUrl="/images/nextjs-book.jpg"
primaryButtonText="Buy Now"
secondaryButtonText="Preview"
onPrimaryClick={() => console.log('Buy clicked')}
onSecondaryClick={() => console.log('Preview clicked')}
/>
<Card
title="React Course"
description="Master React with our interactive online course. Perfect for beginners."
imageUrl="/images/react-course.jpg"
primaryButtonText="Enroll"
onPrimaryClick={() => console.log('Enroll clicked')}
/>
<Card
title="JavaScript Workshop"
description="Hands-on workshop to improve your JavaScript skills with real-world projects."
imageUrl="/images/js-workshop.jpg"
primaryButtonText="Register"
secondaryButtonText="Learn More"
onPrimaryClick={() => console.log('Register clicked')}
onSecondaryClick={() => console.log('Learn More clicked')}
/>
</div>
</div>
);
}
Animations with Styled Components
Styled Components also supports animations using the keyframes
helper:
// components/AnimatedButton.js
import styled, { keyframes } from 'styled-components';
const pulse = keyframes`
0% {
box-shadow: 0 0 0 0 rgba(0, 112, 243, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(0, 112, 243, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 112, 243, 0);
}
`;
const AnimatedButton = styled.button`
background-color: #0070f3;
color: white;
font-size: 1rem;
padding: 0.7rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
animation: ${pulse} 2s infinite;
&:hover {
animation: none;
background-color: #0051a2;
}
`;
export default AnimatedButton;
Best Practices for Styled Components in Next.js
-
Organize styles by component: Keep your styled components close to the React components that use them.
-
Use theme variables: Maintain design consistency by using theme variables instead of hardcoded values.
-
Avoid global styles when possible: Prefer component-level styles to minimize unintended side effects.
-
Name your components meaningfully: Use descriptive names that reflect their purpose.
-
Create abstractions for common patterns: Create reusable styled components for common UI patterns.
-
Leverage composition: Compose smaller styled components to build more complex ones.
-
Separate layout from design: Create styled components that handle layout separately from those that handle visual design.
Performance Considerations
While Styled Components offers many benefits, there are some performance considerations to keep in mind:
-
Bundle size: Styled Components adds to your JavaScript bundle size. This is typically negligible in modern applications, but worth considering for extremely performance-sensitive sites.
-
Server-side rendering: Ensure you've properly set up the
_document.js
file as shown earlier to avoid flashes of unstyled content. -
Babel plugin: Consider using the Styled Components Babel plugin for smaller bundles and better debugging.
Summary
Styled Components provides a powerful way to manage styles in your Next.js applications. By combining the component-based approach of React with actual CSS syntax, it offers a developer-friendly styling solution with great features like dynamic styling, theming, and scoped styles.
In this lesson, we covered:
- Setting up Styled Components with Next.js
- Creating basic and dynamic styled components
- Extending existing styles
- Adding global styles and theme support
- Building practical, reusable components
- Animation support
- Best practices for styling your Next.js applications
Styled Components is just one of several CSS-in-JS solutions available, but its intuitive API and feature set make it a popular choice for Next.js developers looking to create maintainable, component-scoped styles.
Additional Resources
- Official Styled Components Documentation
- Next.js with Styled Components Example
- The styled-components Happy Path
Exercises
- Create a responsive navigation bar using Styled Components with mobile and desktop layouts.
- Build a theme switcher that toggles between light and dark themes.
- Refactor an existing Next.js component to use Styled Components instead of CSS Modules.
- Create a loading spinner component with animations using the keyframes helper.
- Build a reusable form input component with different variants for text, textarea, and select inputs.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)