Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Css Modules
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

CSS Modules in Next.js

Component-scoped styling for maintainable CSS

CSS Modules solve one of CSS's biggest problems: naming conflicts. When building large applications, it's hard to ensure class names don't clash. CSS Modules provide automatic scoping—each component's styles are isolated, class names are unique, and you can use simple names like .button without worrying about conflicts. Next.js has built-in CSS Modules support with zero configuration. Let's master component-scoped styling!

What Are CSS Modules?

CSS Modules are CSS files where class names are scoped locally by default:

❌ Regular CSS (Global)

styles.css
/* All .button classes are global */
.button {
  padding: 10px 20px;
  background: blue;
}

/* This affects ALL buttons in your app! */
Button.tsx
// Regular CSS
import './styles.css';

export function Button() {
  return <button className="button">Click</button>;
}

// ❌ Problem: .button is global
// ❌ Conflicts with other .button classes
// ❌ Hard to maintain in large apps

✅ CSS Modules (Scoped)

Button.module.css
/* Scoped to this component only */
.button {
  padding: 10px 20px;
  background: blue;
}

/* Only affects buttons in this component */
Button.tsx
// CSS Module
import styles from './Button.module.css';

export function Button() {
  return <button className={styles.button}>Click</button>;
}

// ✅ Scoped to this component
// ✅ No naming conflicts
// ✅ Easy to maintain

How CSS Modules Work

CSS Modules generate unique class names automatically. Your .button becomes something like .Button_button__2x3y4. You write simple names, Next.js handles uniqueness!

Basic CSS Module Usage

Step 1: Create a CSS Module

Create a CSS file with the .module.css extension:

components/Button/Button.module.css
.button {
  padding: 12px 24px;
  border-radius: 8px;
  font-weight: 600;
  border: none;
  cursor: pointer;
  transition: all 0.2s;
}

.button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.primary {
  background: #3b82f6;
  color: white;
}

.secondary {
  background: #6b7280;
  color: white;
}

.outline {
  background: transparent;
  border: 2px solid #3b82f6;
  color: #3b82f6;
}

.outline:hover {
  background: #3b82f6;
  color: white;
}

// ✅ Simple, readable class names
// ✅ No need for BEM or complex naming
// ✅ Automatically scoped

Step 2: Import and Use

components/Button/Button.tsx
import styles from './Button.module.css';

interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'outline';
  onClick?: () => void;
}

export function Button({ 
  children, 
  variant = 'primary',
  onClick 
}: ButtonProps) {
  return (
    <button 
      className={`${styles.button} ${styles[variant]}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// Usage:
// <Button variant="primary">Click Me</Button>
// <Button variant="outline">Cancel</Button>

// ✅ Import styles as an object
// ✅ Access classes as properties: styles.button
// ✅ Combine multiple classes with template literals

Advanced CSS Module Patterns

Pattern 1: Conditional Classes

components/Alert/Alert.tsx
import styles from './Alert.module.css';

interface AlertProps {
  type: 'success' | 'error' | 'warning' | 'info';
  children: React.ReactNode;
  dismissible?: boolean;
}

export function Alert({ type, children, dismissible }: AlertProps) {
  return (
    <div className={`${styles.alert} ${styles[type]}`}>
      <div className={styles.content}>
        {children}
      </div>
      {dismissible && (
        <button className={styles.closeButton}>×</button>
      )}
    </div>
  );
}

// ✅ Dynamic class based on type
// ✅ Conditional rendering with styles
// ✅ Clean component code
components/Alert/Alert.module.css
.alert {
  padding: 16px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.success {
  background: #d1fae5;
  border: 1px solid #10b981;
  color: #065f46;
}

.error {
  background: #fee2e2;
  border: 1px solid #ef4444;
  color: #991b1b;
}

.warning {
  background: #fef3c7;
  border: 1px solid #f59e0b;
  color: #92400e;
}

.info {
  background: #dbeafe;
  border: 1px solid #3b82f6;
  color: #1e40af;
}

.content {
  flex: 1;
}

.closeButton {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  padding: 0 8px;
  color: inherit;
  opacity: 0.6;
}

.closeButton:hover {
  opacity: 1;
}

Pattern 2: Composition (Sharing Styles)

components/Card/Card.module.css
/* Base card styles */
.card {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.2s;
}

/* Inherit from card and add hover effect */
.cardHoverable {
  composes: card;
  cursor: pointer;
}

.cardHoverable:hover {
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}

/* Inherit from card and add border */
.cardBordered {
  composes: card;
  border: 2px solid #e5e7eb;
}

/* Inherit and customize */
.cardPrimary {
  composes: card;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

// ✅ composes: shares styles between classes
// ✅ DRY principle - no duplication
// ✅ Flexible variations
components/Card/Card.tsx
import styles from './Card.module.css';

interface CardProps {
  children: React.ReactNode;
  variant?: 'default' | 'hoverable' | 'bordered' | 'primary';
}

export function Card({ children, variant = 'default' }: CardProps) {
  const cardStyles = {
    default: styles.card,
    hoverable: styles.cardHoverable,
    bordered: styles.cardBordered,
    primary: styles.cardPrimary,
  };

  return (
    <div className={cardStyles[variant]}>
      {children}
    </div>
  );
}

// ✅ Cleaner than string concatenation
// ✅ Type-safe variants
// ✅ Easy to maintain

Pattern 3: Global Selectors (When Needed)

components/RichText/RichText.module.css
.richText {
  /* Container styles */
  max-width: 65ch;
  line-height: 1.7;
}

/* Target global HTML elements inside */
.richText :global(h1) {
  font-size: 2.5rem;
  margin-bottom: 1rem;
}

.richText :global(h2) {
  font-size: 2rem;
  margin-bottom: 0.75rem;
}

.richText :global(p) {
  margin-bottom: 1rem;
}

.richText :global(a) {
  color: #3b82f6;
  text-decoration: underline;
}

.richText :global(code) {
  background: #f3f4f6;
  padding: 2px 6px;
  border-radius: 4px;
  font-family: monospace;
}

// ✅ :global() for targeting HTML elements
// ✅ Useful for markdown/HTML content
// ✅ Still scoped to .richText container
components/RichText/RichText.tsx
import styles from './RichText.module.css';

interface RichTextProps {
  html: string;
}

export function RichText({ html }: RichTextProps) {
  return (
    <div 
      className={styles.richText}
      dangerouslySetInnerHTML={{ __html: html }}
    />
  );
}

// ✅ Styles all HTML elements inside
// ✅ Perfect for CMS content or markdown
// ✅ Contained within component

Complete Component Examples

Example 1: Button Component with Variants

components/Button/Button.tsx
import styles from './Button.module.css';

interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
  size?: 'small' | 'medium' | 'large';
  fullWidth?: boolean;
  disabled?: boolean;
  onClick?: () => void;
}

export function Button({
  children,
  variant = 'primary',
  size = 'medium',
  fullWidth = false,
  disabled = false,
  onClick,
}: ButtonProps) {
  const buttonClasses = [
    styles.button,
    styles[variant],
    styles[size],
    fullWidth && styles.fullWidth,
    disabled && styles.disabled,
  ]
    .filter(Boolean)
    .join(' ');

  return (
    <button
      className={buttonClasses}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}
components/Button/Button.module.css
.button {
  border-radius: 8px;
  font-weight: 600;
  border: none;
  cursor: pointer;
  transition: all 0.2s;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

/* Variants */
.primary {
  background: #3b82f6;
  color: white;
}

.primary:hover {
  background: #2563eb;
}

.secondary {
  background: #6b7280;
  color: white;
}

.secondary:hover {
  background: #4b5563;
}

.danger {
  background: #ef4444;
  color: white;
}

.danger:hover {
  background: #dc2626;
}

.ghost {
  background: transparent;
  color: #374151;
  border: 1px solid #d1d5db;
}

.ghost:hover {
  background: #f3f4f6;
}

/* Sizes */
.small {
  padding: 6px 12px;
  font-size: 14px;
}

.medium {
  padding: 10px 20px;
  font-size: 16px;
}

.large {
  padding: 14px 28px;
  font-size: 18px;
}

/* Modifiers */
.fullWidth {
  width: 100%;
}

.disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

// ✅ Complete button system
// ✅ Multiple variants and sizes
// ✅ Modifier classes
// ✅ Accessible (disabled state)

Example 2: Card Component with Nested Elements

components/Card/Card.tsx
import styles from './Card.module.css';

interface CardProps {
  title?: string;
  description?: string;
  children?: React.ReactNode;
  image?: string;
  footer?: React.ReactNode;
}

export function Card({ 
  title, 
  description, 
  children, 
  image,
  footer 
}: CardProps) {
  return (
    <div className={styles.card}>
      {image && (
        <div className={styles.imageContainer}>
          <img src={image} alt={title} className={styles.image} />
        </div>
      )}

      <div className={styles.content}>
        {title && (
          <h3 className={styles.title}>{title}</h3>
        )}
        
        {description && (
          <p className={styles.description}>{description}</p>
        )}

        {children && (
          <div className={styles.body}>{children}</div>
        )}
      </div>

      {footer && (
        <div className={styles.footer}>{footer}</div>
      )}
    </div>
  );
}

// Usage:
// <Card
//   title="Product Name"
//   description="Product description"
//   image="/product.jpg"
//   footer={<Button>Add to Cart</Button>}
// />
components/Card/Card.module.css
.card {
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.2s;
}

.card:hover {
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}

.imageContainer {
  width: 100%;
  height: 200px;
  overflow: hidden;
  background: #f3f4f6;
}

.image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s;
}

.card:hover .image {
  transform: scale(1.05);
}

.content {
  padding: 20px;
}

.title {
  font-size: 20px;
  font-weight: 700;
  margin-bottom: 8px;
  color: #111827;
}

.description {
  font-size: 14px;
  color: #6b7280;
  line-height: 1.6;
  margin-bottom: 12px;
}

.body {
  margin-top: 16px;
}

.footer {
  padding: 16px 20px;
  border-top: 1px solid #e5e7eb;
  background: #f9fafb;
}

// ✅ Organized nested elements
// ✅ Hover effects
// ✅ Responsive image
// ✅ Clean structure

Example 3: Form Input Component

components/Input/Input.tsx
import styles from './Input.module.css';

interface InputProps {
  label?: string;
  error?: string;
  helperText?: string;
  fullWidth?: boolean;
  required?: boolean;
  type?: 'text' | 'email' | 'password' | 'number';
  placeholder?: string;
  value?: string;
  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export function Input({
  label,
  error,
  helperText,
  fullWidth = false,
  required = false,
  type = 'text',
  placeholder,
  value,
  onChange,
}: InputProps) {
  return (
    <div className={`${styles.container} ${fullWidth ? styles.fullWidth : ''}`}>
      {label && (
        <label className={styles.label}>
          {label}
          {required && <span className={styles.required}>*</span>}
        </label>
      )}

      <input
        type={type}
        className={`${styles.input} ${error ? styles.inputError : ''}`}
        placeholder={placeholder}
        value={value}
        onChange={onChange}
        required={required}
      />

      {error && (
        <span className={styles.error}>{error}</span>
      )}

      {helperText && !error && (
        <span className={styles.helperText}>{helperText}</span>
      )}
    </div>
  );
}
components/Input/Input.module.css
.container {
  display: inline-block;
  min-width: 200px;
}

.fullWidth {
  width: 100%;
}

.label {
  display: block;
  font-size: 14px;
  font-weight: 600;
  color: #374151;
  margin-bottom: 6px;
}

.required {
  color: #ef4444;
  margin-left: 4px;
}

.input {
  width: 100%;
  padding: 10px 14px;
  border: 2px solid #d1d5db;
  border-radius: 8px;
  font-size: 16px;
  transition: all 0.2s;
  background: white;
}

.input:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.inputError {
  border-color: #ef4444;
}

.inputError:focus {
  border-color: #ef4444;
  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}

.error {
  display: block;
  margin-top: 6px;
  font-size: 14px;
  color: #ef4444;
}

.helperText {
  display: block;
  margin-top: 6px;
  font-size: 14px;
  color: #6b7280;
}

// ✅ Complete input component
// ✅ Error states
// ✅ Focus styles
// ✅ Accessible (label, required)

CSS Modules Project Structure

Organizing components with CSS Modules

componentsImportant

Select a file or folder to see details

CSS Modules Best Practices

1. Co-locate Styles with Components

TYPESCRIPT
// ✅ GOOD: Component and styles together
components/
  Button/
    Button.tsx
    Button.module.css
  Card/
    Card.tsx
    Card.module.css

// ❌ BAD: Styles separated
components/
  Button.tsx
  Card.tsx
styles/
  Button.module.css
  Card.module.css

// Co-location makes components self-contained and portable

2. Use Simple, Descriptive Names

CSS
/* ✅ GOOD: Simple names (scoping handles uniqueness) */
.button { }
.title { }
.content { }

/* ❌ BAD: Overly specific names (unnecessary with scoping) */
.my-app-button-component-primary { }
.page-header-title-text-large { }

/* CSS Modules handle uniqueness - keep names simple! */

3. Use camelCase for Multi-Word Classes

Button.module.css
/* ✅ GOOD: camelCase */
.primaryButton { }
.errorMessage { }
.navbarLink { }

/* ❌ BAD: kebab-case (requires bracket notation) */
.primary-button { } /* styles['primary-button'] */
.error-message { }  /* styles['error-message'] */
TYPESCRIPT
// camelCase: Clean dot notation
<button className={styles.primaryButton}>

// kebab-case: Bracket notation required
<button className={styles['primary-button']}>

4. Avoid Over-Nesting

CSS
/* ✅ GOOD: Flat structure */
.card { }
.cardHeader { }
.cardTitle { }
.cardBody { }
.cardFooter { }

/* ❌ BAD: Deep nesting */
.card { }
.card .header { }
.card .header .title { }
.card .body { }
.card .footer { }

/* Flat is easier to maintain and override */

5. Combine with Tailwind for Complex Layouts

TYPESCRIPT
// ✅ GOOD: CSS Modules for component styles, Tailwind for layout
import styles from './Card.module.css';

export function Card() {
  return (
    <div className={`${styles.card} container mx-auto px-4`}>
      <div className="grid grid-cols-3 gap-6">
        <div className={styles.content}>...</div>
      </div>
    </div>
  );
}

// CSS Module: Component-specific styles (button, card, etc.)
// Tailwind: Layout, spacing, responsive design

Key Takeaways

  • .module.css extension - tells Next.js to scope styles
  • Automatic scoping - unique class names generated
  • Import as object - styles.className for access
  • No naming conflicts - same names in different components OK
  • composes: - share styles between classes
  • :global() - target global elements when needed
  • Co-location - keep styles with components
  • camelCase names - cleaner dot notation access

What's Next?

You've mastered CSS Modules! Next, we'll explore Tailwind CSS Setup and Configuration—the popular utility-first CSS framework. You'll learn how to set up Tailwind in Next.js, configure it for your project, customize the theme, and combine it with CSS Modules for the best of both worlds.

Tailwind offers rapid development with utility classes, while CSS Modules provide component-scoped custom styles. Together, they're incredibly powerful!

🎯 When to Use CSS Modules

Use CSS Modules for custom component styles that need detailed control. Use Tailwind for rapid prototyping and layout. Many projects use both—CSS Modules for complex components, Tailwind for everything else!

Test Your Understanding

Question 1 of 4

What file extension do CSS Modules use?

Master CSS Modules in Next.js! Learn component-scoped styling for maintainable, conflict-free CSS.

Previous
Redirects and Navigation Guards
Next
Tailwind CSS Setup and Configuration

Master Next.js Styling

Join 2,000+ developers building beautiful Next.js apps. Get the next lesson on Tailwind CSS setup - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo