intermediate Step 9 of 15

Styling in Next.js

Next.js Development

Styling in Next.js

Next.js supports multiple styling approaches out of the box, giving you the freedom to choose the method that best fits your team and project. CSS Modules provide locally scoped CSS without naming conflicts. Tailwind CSS offers utility-first styling directly in your JSX. CSS-in-JS libraries like styled-components and Emotion let you write CSS in JavaScript. Global CSS files handle application-wide styles and resets. Each approach has trade-offs in terms of performance, developer experience, and maintainability. Understanding these options helps you make an informed choice for your project.

CSS Modules

CSS Modules are built into Next.js and provide component-scoped CSS. File names must end with .module.css. Class names are automatically made unique at build time, preventing style conflicts.

/* components/Button.module.css */
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.primary {
  background-color: #0070f3;
  color: white;
}

.primary:hover {
  background-color: #005bb5;
}

.secondary {
  background-color: #eaeaea;
  color: #333;
}

.large {
  padding: 14px 28px;
  font-size: 18px;
}
// components/Button.tsx
import styles from "./Button.module.css";

interface ButtonProps {
  variant?: "primary" | "secondary";
  size?: "default" | "large";
  children: React.ReactNode;
  onClick?: () => void;
}

export default function Button({
  variant = "primary",
  size = "default",
  children,
  onClick,
}: ButtonProps) {
  const className = [
    styles.button,
    styles[variant],
    size === "large" ? styles.large : "",
  ]
    .filter(Boolean)
    .join(" ");

  return (
    <button className={className} onClick={onClick}>
      {children}
    </button>
  );
}

Tailwind CSS

Tailwind CSS is a utility-first CSS framework that Next.js supports out of the box when selected during project creation. It eliminates the need for separate CSS files by applying styles directly as class names.

// components/Card.tsx — styled entirely with Tailwind classes
interface CardProps {
  title: string;
  description: string;
  image?: string;
  tags: string[];
}

export default function Card({ title, description, image, tags }: CardProps) {
  return (
    <div className="bg-white rounded-lg shadow-md overflow-hidden
                    hover:shadow-lg transition-shadow duration-300">
      {image && (
        <img
          src={image}
          alt={title}
          className="w-full h-48 object-cover"
        />
      )}
      <div className="p-6">
        <h3 className="text-xl font-semibold text-gray-900 mb-2">
          {title}
        </h3>
        <p className="text-gray-600 mb-4">{description}</p>
        <div className="flex flex-wrap gap-2">
          {tags.map((tag) => (
            <span
              key={tag}
              className="px-3 py-1 bg-blue-100 text-blue-800
                         text-sm rounded-full"
            >
              {tag}
            </span>
          ))}
        </div>
      </div>
    </div>
  );
}

// Conditional classes with template literals
function StatusBadge({ status }: { status: "active" | "inactive" }) {
  const colors = {
    active: "bg-green-100 text-green-800",
    inactive: "bg-red-100 text-red-800",
  };

  return (
    <span className={`px-2 py-1 rounded text-sm ${colors[status]}`}>
      {status}
    </span>
  );
}

Global Styles

/* app/globals.css — imported in root layout */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --primary: #0070f3;
  --background: #ffffff;
  --foreground: #171717;
}

body {
  font-family: var(--font-sans);
  background: var(--background);
  color: var(--foreground);
}

/* Custom utility classes */
@layer components {
  .container {
    @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8;
  }

  .prose h2 {
    @apply text-2xl font-bold mt-8 mb-4;
  }
}

CSS-in-JS with Server Components

// CSS-in-JS libraries require "use client" in Next.js App Router
// They add JavaScript to the client bundle

// For Server Components, prefer CSS Modules or Tailwind
// which have zero runtime JavaScript overhead

// If using styled-components, configure next.config.js:
const nextConfig = {
  compiler: {
    styledComponents: true,
  },
};
Tip: For the App Router, CSS Modules and Tailwind CSS are the best choices because they work with Server Components and add zero runtime JavaScript. CSS-in-JS libraries require Client Components and add to the JavaScript bundle, which impacts performance.

Key Takeaways

  • CSS Modules provide scoped styles with .module.css files — built-in and zero-runtime.
  • Tailwind CSS offers utility-first styling directly in JSX with excellent developer experience.
  • Global styles go in globals.css imported in the root layout.
  • CSS-in-JS libraries require Client Components and add runtime JavaScript.
  • For Server Components, prefer CSS Modules or Tailwind for best performance.