Styling Your Next.js Applications

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.

  1. Locate your global CSS file: When you created your Next.js app, a src/app/globals.css file was likely generated for you.

  2. 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>
      );
    }
    
  3. 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;
    }
    
  4. Apply a global class to your home page:

    • Open src/app/page.tsx.
    • Add the container and text-center classes 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>
      );
    }
    
  5. 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 the container class.

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):

  1. Add a Footer:

    • In src/app/layout.tsx, add a simple footer element below body’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;).
  2. Change Primary Color:

    • In src/app/globals.css, change the a tag’s color property to a different hex code (e.g., #FF5733 for orange). Observe how this instantly updates across all pages.

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

  1. Create a CSS Module file: Files with the .module.css extension are treated as CSS Modules. Let’s create one for a custom button component.

    • Create a new folder src/app/components if you haven’t already.
    • Create src/app/components/CustomButton.module.css.
    touch src/app/components/CustomButton.module.css
    
  2. 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.

  3. Create a React component to use the module:

    • Create src/app/components/CustomButton.tsx.
    touch src/app/components/CustomButton.tsx
    
  4. 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. The styles object now contains the locally scoped class names as properties (e.g., styles.button, styles.secondary).
    • We can combine multiple classes using template literals.
  5. Use the CustomButton in a page: Let’s add it to our src/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>
      );
    }
    
  6. 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 for styles.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):

  1. Add another button variant:

    • In src/app/components/CustomButton.module.css, add a new class, e.g., .danger with background-color: #f44336; (red).
    • Modify CustomButton.tsx to accept a variant='danger' prop.
    • Add a new CustomButton instance to src/app/page.tsx using this new danger variant.
  2. Use clsx for cleaner class combining:

    • Install clsx: npm install clsx.
    • In src/app/components/CustomButton.tsx, replace the manual string concatenation for buttonClasses with clsx for a cleaner way to conditionally apply classes.
    • Hint: import clsx from 'clsx'; and then const buttonClasses = clsx(styles.button, { [styles.secondary]: variant === 'secondary', [styles.large]: size === 'large' });

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)

  1. Install Tailwind CSS:

    npm install -D tailwindcss postcss autoprefixer
    
  2. Initialize Tailwind CSS: This creates tailwind.config.ts and postcss.config.js.

    npx tailwindcss init -p
    
  3. Configure 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 content array includes all directories where you’ll use Tailwind classes. The src entry is crucial if your components are within src/app or src/components.

  4. 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 @tailwind directives 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.

  1. Remove manual style attributes and custom container/text-center classes from src/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.
  2. Save the file. Your http://localhost:3000 page 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):

  1. 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-700 and add mb-2.
    • Add py-4 border-b border-gray-200 to each li element.
    • Wrap the whole page content in a max-w-3xl mx-auto p-6.
  2. 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-4 and 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 TailwindCard on your home page (src/app/page.tsx).
  3. Explore Tailwind Customization:

    • Open tailwind.config.ts.
    • Add a custom color to the theme.extend.colors object, e.g., primaryGreen: '#10B981'.
    • Use this new color in src/app/page.tsx, e.g., bg-primaryGreen or text-primaryGreen.

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 left position).
  • 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/image and next/font components for optimized media and typography. These components integrate perfectly with your styling strategy.
  • className vs. style: Prefer className (with global CSS, CSS Modules, or Tailwind) over style for 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.