4. Styling Your Next.js Applications
A well-designed user interface is crucial for any successful web application. Next.js offers various flexible and efficient ways to style your components, ranging from traditional CSS to modern utility-first frameworks. In this chapter, we’ll explore the most common and recommended styling strategies, complete with practical examples.
4.1 Global CSS
Global CSS applies styles across your entire application. This is ideal for defining basic typographic styles, CSS resets, or universal layout rules.
How to Use Global CSS
In a Next.js App Router project, global CSS files must be imported in your root layout.tsx file.
Locate your global CSS file: When you created your Next.js app, a
src/app/globals.cssfile was likely generated for you.Ensure it’s imported in
src/app/layout.tsx:// src/app/layout.tsx import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; // This line imports your global CSS const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "My Next.js App", description: "Learning Next.js styling", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body className={inter.className}>{children}</body> </html> ); }Add some global styles to
src/app/globals.css:/* src/app/globals.css */ /* Basic CSS Reset */ html, body { margin: 0; padding: 0; box-sizing: border-box; } /* Global Body Styles */ body { background-color: #f8f8f8; color: #333; line-height: 1.6; } /* Headings */ h1, h2, h3, h4, h5, h6 { color: #222; margin-top: 1.5em; margin-bottom: 0.8em; } h1 { font-size: 2.5em; } h2 { font-size: 2em; } h3 { font-size: 1.5em; } /* Links */ a { color: #0070f3; text-decoration: none; } a:hover { text-decoration: underline; } /* Utility classes (can be added here, but be mindful of global scope) */ .text-center { text-align: center; } .container { max-width: 960px; margin: 0 auto; padding: 0 20px; }Apply a global class to your home page:
- Open
src/app/page.tsx. - Add the
containerandtext-centerclasses to your main element.
// src/app/page.tsx import Link from 'next/link'; export default function Home() { return ( <main className="container text-center" style={{ fontFamily: 'sans-serif', padding: '40px 0' }}> <h1>Welcome to Next.js!</h1> <p> This is the homepage of your Next.js application. <br /> Let's learn and build amazing things! </p> <div style={{ marginTop: '30px' }}> <Link href="/blog" style={{ textDecoration: 'none', color: '#0070f3', marginRight: '15px' }}> View Blog Posts </Link> <Link href="/products" style={{ textDecoration: 'none', color: '#0070f3', marginRight: '15px' }}> Explore Products </Link> <Link href="/users" style={{ textDecoration: 'none', color: '#0070f3' }}> See Users </Link> </div> </main> ); }- Open
Save all files and visit
http://localhost:3000. You should see the global styles applied: a grey background, improved typography, blue links, and the content centered and constrained by thecontainerclass.
When to use Global CSS:
- For site-wide defaults (font families, body background, link colors).
- CSS resets (like Normalize.css or a custom reset).
- Base styles for HTML elements (e.g.,
h1,p,ul). - Importing third-party CSS libraries that are meant to be global.
Exercises/Mini-Challenges (Global CSS):
Add a Footer:
- In
src/app/layout.tsx, add a simple footer element belowbody’s children. - Add styles for this footer in
src/app/globals.css(e.g.,text-align: center; padding: 20px; background-color: #eee; margin-top: 50px;).
- In
Change Primary Color:
- In
src/app/globals.css, change theatag’scolorproperty to a different hex code (e.g.,#FF5733for orange). Observe how this instantly updates across all pages.
- In
4.2 CSS Modules
CSS Modules provide a way to locally scope CSS class names by automatically generating unique names. This prevents style collisions and makes your CSS more modular and maintainable. Next.js has built-in support for CSS Modules.
How to Use CSS Modules
Create a CSS Module file: Files with the
.module.cssextension are treated as CSS Modules. Let’s create one for a custom button component.- Create a new folder
src/app/componentsif you haven’t already. - Create
src/app/components/CustomButton.module.css.
touch src/app/components/CustomButton.module.css- Create a new folder
Add styles to
src/app/components/CustomButton.module.css:/* src/app/components/CustomButton.module.css */ .button { background-color: #4CAF50; /* Green */ border: none; color: white; padding: 15px 32px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; border-radius: 8px; transition: background-color 0.3s ease; } .button:hover { background-color: #45a049; } .secondary { background-color: #008CBA; /* Blue */ } .secondary:hover { background-color: #007bb5; } .large { font-size: 20px; padding: 18px 36px; }Notice: These class names (
button,secondary,large) will not conflict with other class names in your global CSS or other CSS Modules because they are locally scoped.Create a React component to use the module:
- Create
src/app/components/CustomButton.tsx.
touch src/app/components/CustomButton.tsx- Create
Add content to
src/app/components/CustomButton.tsx:// src/app/components/CustomButton.tsx import styles from './CustomButton.module.css'; // Import the CSS module interface CustomButtonProps { children: React.ReactNode; onClick: () => void; variant?: 'primary' | 'secondary'; size?: 'medium' | 'large'; } export default function CustomButton({ children, onClick, variant = 'primary', size = 'medium', }: CustomButtonProps) { // Use styles.className to access the locally scoped classes const buttonClasses = `${styles.button} ${ variant === 'secondary' ? styles.secondary : '' } ${size === 'large' ? styles.large : ''}`; return ( <button className={buttonClasses} onClick={onClick}> {children} </button> ); }Explanation:
import styles from './CustomButton.module.css';imports the CSS Module. Thestylesobject now contains the locally scoped class names as properties (e.g.,styles.button,styles.secondary).- We can combine multiple classes using template literals.
Use the
CustomButtonin a page: Let’s add it to oursrc/app/page.tsx.// src/app/page.tsx import Link from 'next/link'; import CustomButton from './components/CustomButton'; // Import our new button export default function Home() { const handleButtonClick = (buttonName: string) => { alert(`${buttonName} button clicked!`); }; return ( <main className="container text-center" style={{ fontFamily: 'sans-serif', padding: '40px 0' }}> <h1>Welcome to Next.js!</h1> <p> This is the homepage of your Next.js application. <br /> Let's learn and build amazing things! </p> <div style={{ marginTop: '30px', display: 'flex', justifyContent: 'center', gap: '10px' }}> <Link href="/blog" style={{ textDecoration: 'none', color: '#0070f3', marginRight: '15px' }}> View Blog Posts </Link> <Link href="/products" style={{ textDecoration: 'none', color: '#0070f3', marginRight: '15px' }}> Explore Products </Link> <Link href="/users" style={{ textDecoration: 'none', color: '#0070f3' }}> See Users </Link> </div> <div style={{ marginTop: '50px', borderTop: '1px dashed #ccc', paddingTop: '30px' }}> <h2>Check out our custom buttons:</h2> <CustomButton onClick={() => handleButtonClick('Primary')} variant="primary"> Primary Action </CustomButton> <CustomButton onClick={() => handleButtonClick('Secondary')} variant="secondary" size="large"> Large Secondary Action </CustomButton> </div> </main> ); }Save all files and refresh
http://localhost:3000. You should see your beautifully styled custom buttons. Inspect them in your browser’s developer tools, and you’ll notice unique class names generated forstyles.button,styles.secondary, etc., preventing conflicts.
When to use CSS Modules:
- For component-specific styles where you want to avoid global class name collisions.
- When you prefer writing standard CSS/Sass.
- For creating reusable UI components with encapsulated styles.
Exercises/Mini-Challenges (CSS Modules):
Add another button variant:
- In
src/app/components/CustomButton.module.css, add a new class, e.g.,.dangerwithbackground-color: #f44336;(red). - Modify
CustomButton.tsxto accept avariant='danger'prop. - Add a new
CustomButtoninstance tosrc/app/page.tsxusing this new danger variant.
- In
Use
clsxfor cleaner class combining:- Install
clsx:npm install clsx. - In
src/app/components/CustomButton.tsx, replace the manual string concatenation forbuttonClasseswithclsxfor a cleaner way to conditionally apply classes. - Hint:
import clsx from 'clsx';and thenconst buttonClasses = clsx(styles.button, { [styles.secondary]: variant === 'secondary', [styles.large]: size === 'large' });
- Install
4.3 Tailwind CSS
Tailwind CSS is a utility-first CSS framework that lets you rapidly build custom designs directly in your markup using predefined classes. Instead of writing custom CSS, you compose styles by adding multiple small, single-purpose utility classes (e.g., flex, pt-4, text-center, bg-blue-500).
Next.js has excellent built-in support for Tailwind CSS, and create-next-app even offers to set it up for you. If you chose “No” during initial setup, here’s how to add it manually:
Installing and Configuring Tailwind CSS (If not already installed)
(Skip these steps if you selected Tailwind CSS during create-next-app)
Install Tailwind CSS:
npm install -D tailwindcss postcss autoprefixerInitialize Tailwind CSS: This creates
tailwind.config.tsandpostcss.config.js.npx tailwindcss init -pConfigure your template paths (
tailwind.config.ts): This tells Tailwind which files to scan for class names so it can generate the necessary CSS and purge unused styles in production.// tailwind.config.ts import type { Config } from 'tailwindcss'; const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', './src/**/*.{js,ts,jsx,tsx,mdx}', // Important if you're using a 'src' directory ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [], }; export default config;Note: Ensure
contentarray includes all directories where you’ll use Tailwind classes. Thesrcentry is crucial if your components are withinsrc/apporsrc/components.Add Tailwind directives to your global CSS (
src/app/globals.css):/* src/app/globals.css */ @tailwind base; @tailwind components; @tailwind utilities; /* You can still add your custom global CSS below these directives */ html, body { margin: 0; padding: 0; box-sizing: border-box; } body { background-color: #f8f8f8; color: #333; line-height: 1.6; } /* ... other global styles you want to keep */Important: Place the
@tailwinddirectives at the very beginning of your global CSS file.
Using Tailwind CSS
Now that Tailwind is set up, you can start using its utility classes directly in your JSX.
Example: Re-styling the home page with Tailwind
Let’s modify src/app/page.tsx to use Tailwind classes for layout and styling.
Remove manual
styleattributes and customcontainer/text-centerclasses fromsrc/app/page.tsx(or comment them out to see the difference):// src/app/page.tsx import Link from 'next/link'; import CustomButton from './components/CustomButton'; // Still using our custom button export default function Home() { const handleButtonClick = (buttonName: string) => { alert(`${buttonName} button clicked!`); }; return ( // Apply Tailwind classes directly <main className="min-h-screen flex flex-col items-center justify-center p-6 bg-gradient-to-br from-blue-50 to-indigo-100 text-gray-800"> <h1 className="text-5xl font-extrabold text-blue-800 mb-4">Welcome to Next.js!</h1> <p className="text-xl text-gray-700 mb-8 max-w-2xl text-center"> This is the homepage of your Next.js application. <br /> Let's learn and build amazing things! </p> <div className="flex space-x-4 mb-12"> <Link href="/blog" className="text-blue-600 hover:text-blue-800 font-medium text-lg"> View Blog Posts </Link> <Link href="/products" className="text-blue-600 hover:text-blue-800 font-medium text-lg"> Explore Products </Link> <Link href="/users" className="text-blue-600 hover:text-blue-800 font-medium text-lg"> See Users </Link> </div> <div className="mt-12 pt-8 border-t border-dashed border-gray-300 text-center"> <h2 className="text-3xl font-bold text-gray-900 mb-6">Check out our custom buttons:</h2> <div className="flex justify-center gap-4"> <CustomButton onClick={() => handleButtonClick('Primary')} variant="primary"> Primary Action </CustomButton> <CustomButton onClick={() => handleButtonClick('Secondary')} variant="secondary" size="large"> Large Secondary Action </CustomButton> </div> </div> </main> ); }Explanation of Tailwind classes:
min-h-screen: Sets minimum height to 100vh.flex flex-col items-center justify-center: Uses flexbox to center content vertically and horizontally.p-6: Adds padding of 1.5rem.bg-gradient-to-br from-blue-50 to-indigo-100: Applies a background gradient.text-5xl font-extrabold text-blue-800 mb-4: Styles heading size, weight, color, and margin-bottom.space-x-4: Adds horizontal space between direct children.
Save the file. Your
http://localhost:3000page should now look significantly different, with a modern, responsive design provided by Tailwind.
When to use Tailwind CSS:
- For rapid prototyping and development, as you can style components directly in your JSX.
- When you want a consistent design system with highly customizable defaults.
- For applications where you want to minimize custom CSS and avoid CSS complexity.
- It pairs exceptionally well with React Server Components as classes are processed at build time.
Exercises/Mini-Challenges (Tailwind CSS):
Restyle the Blog Page with Tailwind:
- Open
src/app/blog/page.tsx. - Replace its inline styles or any existing simple CSS with Tailwind utility classes.
- Make the post titles
text-2xl font-bold text-indigo-700and addmb-2. - Add
py-4 border-b border-gray-200to eachlielement. - Wrap the whole page content in a
max-w-3xl mx-auto p-6.
- Open
Create a Responsive Card:
- Create a new component
src/app/components/TailwindCard.tsx. - Use Tailwind classes to create a simple card with
bg-white shadow-md rounded-lg p-6 m-4and add responsive padding, e.g.,sm:p-8 md:p-10. - Include some text and a button inside it, also styled with Tailwind.
- Place this
TailwindCardon your home page (src/app/page.tsx).
- Create a new component
Explore Tailwind Customization:
- Open
tailwind.config.ts. - Add a custom color to the
theme.extend.colorsobject, e.g.,primaryGreen: '#10B981'. - Use this new color in
src/app/page.tsx, e.g.,bg-primaryGreenortext-primaryGreen.
- Open
4.4 Inline Styles (Use Sparingly)
You can always use inline styles (style={{ property: 'value' }}) in React components. This is often used for dynamic styles based on JavaScript logic or for very small, one-off adjustments.
Example:
// In any component
function MyComponent() {
const isHighlighted = true;
return (
<div style={{
backgroundColor: isHighlighted ? 'yellow' : 'transparent',
padding: '10px',
borderRadius: '5px'
}}>
This div has inline styles.
</div>
);
}
When to use Inline Styles:
- For dynamic styles that change frequently based on component state or props (e.g., a progress bar’s width, an element’s
leftposition). - For very small, isolated styling needs that don’t warrant a separate CSS file or class.
- Avoid using them for global styles or complex layouts, as they can quickly become hard to manage, are not easily overridden, and don’t leverage CSS optimizations.
4.5 CSS-in-JS Libraries (e.g., Styled Components, Emotion)
While Next.js with the App Router highly recommends Global CSS, CSS Modules, and Tailwind CSS for performance, CSS-in-JS libraries like Styled Components or Emotion are still viable. However, integrating them correctly for optimal performance (especially with Server Components and streaming) requires careful configuration to ensure styles are extracted and injected server-side.
Next.js provides official examples and documentation for integrating these if you have a strong preference or existing codebase using them. For new projects focusing on performance, the default styling options are often preferred.
Best Practices for Styling in Next.js
- Prioritize Server Components: Whenever possible, use Server Components for data fetching and rendering static/layout UI. Tailwind CSS and global CSS work seamlessly with Server Components.
- Balance Styling Methods: You don’t have to stick to just one.
- Use Global CSS for universal styles (resets, base typography, site-wide variables).
- Use Tailwind CSS for most component-level styling due to its speed and maintainability.
- Use CSS Modules for highly specific, complex component styles where a utility-first approach might lead to very long class lists, or if you prefer writing traditional CSS/Sass for a component.
- Image and Font Optimization: Always use Next.js’s built-in
next/imageandnext/fontcomponents for optimized media and typography. These components integrate perfectly with your styling strategy. classNamevs.style: PreferclassName(with global CSS, CSS Modules, or Tailwind) overstylefor most styling to benefit from CSS optimizations, caching, and maintainability.- Responsiveness: Plan your designs with a mobile-first approach. Tailwind CSS excels at this with its responsive utility variants (e.g.,
sm:,md:,lg:). - Consistency: Establish a design system early on, whether it’s through Tailwind’s configuration or well-defined CSS Module classes, to ensure a cohesive look and feel.
By thoughtfully combining these styling techniques, you can create visually stunning, performant, and easily maintainable Next.js applications.