Next.js & Classnames: A Beginner’s Guide to Conditional Styling

In the dynamic world of web development, creating visually appealing and responsive user interfaces is paramount. Often, the styling of your components needs to adapt based on various conditions – user interactions, data states, or even screen sizes. This is where conditional styling comes in, enabling you to change the appearance of your elements dynamically. While there are several ways to achieve this, the classnames npm package offers a clean, efficient, and readable solution, particularly when working with Next.js.

Why Conditional Styling Matters

Imagine building a button. You might want the button to change color when a user hovers over it, or disable it when a form is submitting. Or, consider a list item that needs a special style when selected. Without conditional styling, you’d likely end up with complex logic and potentially messy code. Conditional styling simplifies this process, allowing you to manage the visual state of your components effectively.

Consider a practical example: a navigation bar. You might want to highlight the current active page by applying a different background color and font weight to the corresponding navigation link. Or, think about a loading indicator that appears only while data is being fetched. Conditional styling is the key to making these dynamic changes.

Introducing the classnames Package

The classnames package is a small utility that helps you manage CSS class names conditionally. It takes multiple arguments and returns a string of class names, only including those that evaluate to true. This makes it incredibly easy to add or remove classes based on specific conditions. It’s a lightweight package, making it an excellent choice for Next.js projects where performance is crucial.

Installation

Before we begin, you need to install the package in your Next.js project. Open your terminal and run the following command:

npm install classnames

or if you are using yarn:

yarn add classnames

This command downloads and installs the classnames package, making it available for use in your project.

Basic Usage

Let’s dive into the core functionality of classnames. The basic syntax is straightforward. You import the function and then call it with a series of arguments. These arguments can be strings (representing class names) or objects (where the keys are class names and the values are boolean expressions).

Here’s a simple example:

import classNames from 'classnames';

function MyComponent({
  isActive,
  isError,
}) {
  const buttonClasses = classNames(
    'button',
    {
      'button-active': isActive,
      'button-error': isError,
    }
  );

  return <button className={buttonClasses}>Click Me</button>;
}

In this example, we import classNames. We then define a buttonClasses variable that uses classNames to generate a string of class names. The first argument is the base class name, 'button'. The second argument is an object. Inside the object, the keys ('button-active' and 'button-error') are class names, and the values (isActive and isError) are boolean variables. If isActive is true, the class 'button-active' will be included in the final string. Similarly, if isError is true, the class 'button-error' will be included.

If isActive is true and isError is false, the buttonClasses variable will be equal to 'button button-active'. If both are false, it will be 'button'. If both are true, it will be 'button button-active button-error'.

Real-World Examples

Example 1: Active Navigation Link

Let’s create a navigation bar where the active link is highlighted. First, create a new Next.js project or navigate to an existing one. Then, create a new component called Navbar.js. Inside this file, we’ll create the component that renders our navigation.

import classNames from 'classnames';
import Link from 'next/link';
import styles from './Navbar.module.css'; // Import your CSS module

function Navbar({
  activePath,
}) {
  return (
    <nav className={styles.navbar}>
      <ul>
        <li>
          <Link href="/">
            <a
              className={classNames(styles.navLink, {
                [styles.active]: activePath === '/',
              })}
            >
              Home
            </a>
          </Link>
        </li>
        <li>
          <Link href="/about">
            <a
              className={classNames(styles.navLink, {
                [styles.active]: activePath === '/about',
              })}
            >
              About
            </a>
          </Link>
        </li>
        <li>
          <Link href="/contact">
            <a
              className={classNames(styles.navLink, {
                [styles.active]: activePath === '/contact',
              })}
            >
              Contact
            </a>
          </Link>
        </li>
      </ul>
    </nav>
  );
}

export default Navbar;

In this example, we’re using the Link component from Next.js for navigation. The activePath prop is passed to the Navbar component, indicating the current route. Inside the <a> tag, we use classNames to conditionally apply the active class based on whether the current route matches the link’s path. Create a file called Navbar.module.css and add the following CSS rules:

.navbar {
  background-color: #f0f0f0;
  padding: 1rem;
}

.navLink {
  color: #333;
  text-decoration: none;
  padding: 0.5rem 1rem;
  margin-right: 1rem;
  border-radius: 4px;
  transition: background-color 0.3s ease;
}

.active {
  background-color: #ddd;
  font-weight: bold;
}

.navLink:hover {
  background-color: #ccc;
}

Now, in your _app.js or the page where you are rendering the navigation, you’ll need to determine the active path. Here’s how you might do that:

import { useRouter } from 'next/router';
import Navbar from '../components/Navbar';

function MyApp({ Component, pageProps }) {
  const router = useRouter();
  const activePath = router.pathname;

  return (
    <>
      <Navbar activePath={activePath} />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

This code snippet utilizes the useRouter hook from Next.js to get the current route (router.pathname). This path is then passed as a prop to the Navbar component, which uses it to determine which link should be highlighted.

When you navigate to different pages, the active link in the navigation bar will be highlighted, demonstrating the power of conditional styling with classnames.

Example 2: Loading Indicator

Let’s create a simple component that shows a loading indicator while fetching data. Create a new component called LoadingIndicator.js:

import classNames from 'classnames';
import styles from './LoadingIndicator.module.css';

function LoadingIndicator({
  isLoading,
}) {
  return (
    <div className={classNames(styles.loadingContainer, {
      [styles.visible]: isLoading,
    })}>
      <div className={styles.spinner} />
      <p>Loading...</p>
    </div>
  );
}

export default LoadingIndicator;

This component accepts an isLoading prop. The classNames function is used to conditionally apply the visible class to the container. Create a file called LoadingIndicator.module.css and add the following CSS rules:


.loadingContainer {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}

.visible {
  opacity: 1;
  visibility: visible;
}

.spinner {
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-top: 4px solid white;
  border-radius: 50%;
  width: 40px;
  height: 40px;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

Now, let’s use this component in a page. Let’s modify a page component (e.g., pages/index.js) to simulate fetching data:

import { useState, useEffect } from 'react';
import LoadingIndicator from '../components/LoadingIndicator';

function HomePage() {
  const [isLoading, setIsLoading] = useState(false);
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);
      // Simulate an API call
      await new Promise((resolve) => setTimeout(resolve, 2000));
      setData({ message: 'Data loaded successfully!' });
      setIsLoading(false);
    };

    fetchData();
  }, []);

  return (
    <>
      <LoadingIndicator isLoading={isLoading} />
      <div>
        {data ? <p>{data.message}</p> : <p>Loading data...</p>}
      </div>
    </>
  );
}

export default HomePage;

In this example, the HomePage component simulates fetching data using useState and useEffect. The isLoading state is set to true while data is being fetched and then set to false when the data is loaded. The LoadingIndicator component is rendered, and its isLoading prop is bound to the isLoading state. The loading indicator will appear during the simulated API call.

Advanced Usage and Considerations

Combining with CSS Modules

As demonstrated in the examples above, classnames works seamlessly with CSS Modules in Next.js. CSS Modules allow you to scope your CSS styles to a specific component, preventing style conflicts. By using CSS Modules, you can ensure that your styles are unique to each component. You import your CSS modules using:

import styles from './MyComponent.module.css';

Then, you can use classnames to combine these styles:

import classNames from 'classnames';
import styles from './MyComponent.module.css';

function MyComponent({
  isActive,
}) {
  return (
    <div className={classNames(styles.container, {
      [styles.active]: isActive,
    })}>
      <p>Hello</p>
    </div>
  );
}

In this example, styles.container and styles.active are CSS class names generated by the CSS Module. classnames combines these based on the condition isActive.

Using with Arrays and Multiple Conditions

classnames can handle multiple conditions effectively. You can pass multiple objects or strings as arguments. This allows you to apply several conditions at once.

import classNames from 'classnames';

function MyComponent({
  isActive,
  isError,
  isHighlighted,
}) {
  return (
    <div className={classNames(
      'base-class',
      {
        'active': isActive,
        'error': isError,
        'highlighted': isHighlighted,
      },
      isActive ? 'active-style' : null
    )}>
      <p>Content</p>
    </div>
  );
}

In this example, we have multiple conditions (isActive, isError, and isHighlighted). We also add a conditional string (isActive ? 'active-style' : null). The result will be a class name string like “base-class active highlighted active-style” if all conditions are true.

Performance Considerations

While classnames is generally very efficient, consider the following for optimal performance:

  • Avoid Excessive Re-renders: Ensure that the conditions you pass to classnames are derived from the component’s state or props. Minimize unnecessary re-renders by optimizing your component’s logic.
  • Keep it Simple: Avoid overly complex conditional logic within the classnames function itself. If your conditions become too complex, consider breaking them down into separate variables or helper functions for better readability.

Common Mistakes and How to Fix Them

Here are some common mistakes when using classnames and how to avoid them:

  • Incorrect Import Path: Make sure you’re importing classnames correctly. Double-check your import statement: import classNames from 'classnames';
  • Typographical Errors: Ensure that your class names in both the JavaScript and CSS files match exactly. A simple typo can break the styling.
  • CSS Specificity Issues: If your styles aren’t applying as expected, check the CSS specificity. Make sure your conditional classes have the necessary specificity to override other styles. You might need to adjust your CSS rules or use more specific selectors.
  • Forgetting to Import CSS Modules: When using CSS Modules, remember to import them into your component, as shown in the examples. Without importing the CSS module, you won’t be able to access the scoped class names.
  • Incorrect Boolean Values: Ensure that the values you’re passing to the object keys in classnames are boolean values (true or false). Non-boolean values might not work as expected.

Key Takeaways

  • Simplicity: classnames makes conditional styling in React and Next.js clean and straightforward.
  • Readability: It enhances code readability by clearly defining the conditions for applying CSS classes.
  • Efficiency: It is a lightweight package, contributing to your application’s performance.
  • Integration: It works seamlessly with CSS Modules and other styling solutions.

FAQ

  1. What is the main benefit of using classnames?

    The main benefit is improved code readability and maintainability when handling conditional CSS classes. It makes your code cleaner and easier to understand.

  2. Does classnames replace the need for CSS Modules?

    No, classnames complements CSS Modules. CSS Modules provide scoped CSS, and classnames helps you conditionally apply those scoped classes.

  3. Can I use classnames with regular CSS files?

    Yes, you can use classnames with regular CSS files. However, with regular CSS files, you might encounter naming conflicts if you don’t use a naming convention. CSS Modules help prevent such conflicts by scoping your CSS.

  4. Is classnames suitable for large-scale projects?

    Yes, classnames is suitable for large-scale projects. Its simplicity and efficiency make it a good choice for projects of any size.

  5. Are there any alternatives to classnames?

    Yes, alternatives include libraries like clsx. However, classnames is still a popular and widely used choice due to its simplicity and ease of use.

By mastering the classnames package, you gain a powerful tool for building dynamic and responsive user interfaces in your Next.js projects. Its simplicity and efficiency make it an indispensable part of a modern front-end developer’s toolkit, enabling you to manage your component’s visual state with ease. As you continue to build more complex applications, the ability to conditionally apply styles will become increasingly crucial, and classnames will be there to simplify the process, helping you write cleaner, more maintainable code, and creating a better user experience.