Build a Next.js Interactive Web-Based Color Palette Generator

Written by

in

Ever found yourself staring at a website, a poster, or a design, captivated by the colors used? Perhaps you’ve wished you could easily extract those colors to use in your own projects. Or maybe you’re a designer looking for a quick way to generate harmonious color schemes. In this tutorial, we’ll build an interactive web-based color palette generator using Next.js, a powerful React framework. This project will not only teach you the fundamentals of Next.js but also provide a practical tool for your design endeavors.

Why Build a Color Palette Generator?

Color is a fundamental element of design. The right color palette can make or break a project. A color palette generator solves several problems:

  • Inspiration: Quickly generate color schemes to spark creativity.
  • Consistency: Ensure visual consistency across your designs.
  • Accessibility: Test color combinations for contrast and readability.
  • Efficiency: Save time by automating the color selection process.

This tutorial is designed for developers with some experience in JavaScript and React. Even if you’re new to Next.js, we’ll break down each step so you can follow along. By the end, you’ll have a functional color palette generator and a solid understanding of Next.js concepts.

Prerequisites

Before we start, make sure you have the following:

  • Node.js and npm (or yarn): You’ll need these to install and manage project dependencies.
  • A code editor: VS Code, Sublime Text, or any editor you prefer.
  • Basic JavaScript and React knowledge: Familiarity with components, props, and state will be helpful.

Setting Up Your Next.js Project

Let’s get started by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app color-palette-generator

This command creates a new Next.js project named color-palette-generator. Navigate into the project directory:

cd color-palette-generator

Now, let’s start the development server:

npm run dev

Open your browser and go to http://localhost:3000. You should see the default Next.js welcome page.

Project Structure

Next.js follows a file-system-based router. This means that the pages in your application are determined by the files in the pages directory. For this project, we’ll primarily work with the index.js file inside the pages directory. Here’s a basic overview of the project structure:

color-palette-generator/
├── node_modules/
├── pages/
│   └── index.js  <-- Our main page
├── public/
│   └── ...
├── styles/
│   └── globals.css
├── .gitignore
├── next.config.js
├── package-lock.json
├── package.json
└── README.md

Building the Color Palette Generator

Let’s start by modifying the pages/index.js file. We’ll replace the default content with the basic structure for our color palette generator.

// pages/index.js
import { useState } from 'react';

function getRandomHexColor() {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}

export default function Home() {
  const [colors, setColors] = useState([
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
  ]);

  const handleGeneratePalette = () => {
    setColors(Array(5).fill(null).map(getRandomHexColor));
  };

  return (
    <div style={{ fontFamily: 'sans-serif', padding: '20px' }}>
      <h1 style={{ textAlign: 'center' }}>Color Palette Generator</h1>
      <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '20px' }}>
        <button onClick={handleGeneratePalette} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '5px' }}>Generate Palette</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap' }}>
        {colors.map((color, index) => (
          <div
            key={index}
            style={{
              width: '100px',
              height: '100px',
              backgroundColor: color,
              margin: '10px',
              borderRadius: '5px',
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              color: 'white',
              fontSize: '14px',
              fontWeight: 'bold',
              cursor: 'pointer',
            }}
            onClick={() => {
              navigator.clipboard.writeText(color);
              alert('Color copied to clipboard!');
            }}
          >
            {color}
          </div>
        ))}
      </div>
    </div>
  );
}

Let’s break down this code:

  • Importing useState: We import the useState hook from React to manage the state of our colors.
  • getRandomHexColor() function: This function generates a random hexadecimal color code.
  • Home component: This is our main component.
  • useState hook: We initialize the colors state with an array of five random colors.
  • handleGeneratePalette function: This function updates the colors state with a new set of random colors when the button is clicked.
  • JSX structure: We use JSX to render the UI. It includes a heading, a button to generate a new palette, and a display of the generated colors. Each color is displayed in a square with its hex code, and when clicked, the hex code is copied to the clipboard.

Save the file and refresh your browser. You should now see a color palette generator with five random colors and a button to generate a new palette. When you click on a color, it copies the hex code to your clipboard.

Styling the Application

While the functionality is there, the application could use some styling. Let’s add some basic CSS to improve the look and feel. We’ll use inline styles in this example for simplicity, but in a real-world project, you’d likely use a CSS-in-JS solution (like styled-components) or a CSS file.

Here’s the updated pages/index.js with some inline styles:


// pages/index.js
import { useState } from 'react';

function getRandomHexColor() {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}

export default function Home() {
  const [colors, setColors] = useState([
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
  ]);

  const handleGeneratePalette = () => {
    setColors(Array(5).fill(null).map(getRandomHexColor));
  };

  return (
    <div style={{ fontFamily: 'sans-serif', padding: '20px', backgroundColor: '#f0f0f0', minHeight: '100vh' }}>
      <h1 style={{ textAlign: 'center', marginBottom: '20px' }}>Color Palette Generator</h1>
      <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '20px' }}>
        <button onClick={handleGeneratePalette} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '5px' }}>Generate Palette</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap' }}>
        {colors.map((color, index) => (
          <div
            key={index}
            style={{
              width: '100px',
              height: '100px',
              backgroundColor: color,
              margin: '10px',
              borderRadius: '5px',
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              color: 'white',
              fontSize: '14px',
              fontWeight: 'bold',
              cursor: 'pointer',
              boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)'
            }}
            onClick={() => {
              navigator.clipboard.writeText(color);
              alert('Color copied to clipboard!');
            }}
          >
            {color}
          </div>
        ))}
      </div>
    </div>
  );
}

Here, we’ve added styles for:

  • Overall page layout (background color, padding, minimum height).
  • Heading (centered, margin).
  • Button (padding, font size, background color, color, border, border-radius).
  • Color boxes (width, height, background color, margin, border-radius, display, justification, alignment, color, font size, font weight, cursor, box-shadow).

Now, when you refresh your browser, the application should look much more polished.

Adding More Features

Let’s enhance our color palette generator with additional features. We can add a feature to save the current palette to local storage, allowing users to revisit their favorite color schemes. We’ll also add a feature to display the color’s name if we can determine the name, providing more context for the colors.

Saving Palettes to Local Storage

First, we’ll add a button to save the current palette. When clicked, it will store the current colors in the browser’s local storage.


// pages/index.js
import { useState, useEffect } from 'react';

function getRandomHexColor() {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}

export default function Home() {
  const [colors, setColors] = useState([
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
  ]);
  const [savedPalettes, setSavedPalettes] = useState([]);

  useEffect(() => {
    const storedPalettes = localStorage.getItem('savedPalettes');
    if (storedPalettes) {
      setSavedPalettes(JSON.parse(storedPalettes));
    }
  }, []);

  useEffect(() => {
    localStorage.setItem('savedPalettes', JSON.stringify(savedPalettes));
  }, [savedPalettes]);

  const handleGeneratePalette = () => {
    setColors(Array(5).fill(null).map(getRandomHexColor));
  };

  const handleSavePalette = () => {
    const newPalette = { id: Date.now(), colors: colors };
    setSavedPalettes([...savedPalettes, newPalette]);
  };

  return (
    <div style={{ fontFamily: 'sans-serif', padding: '20px', backgroundColor: '#f0f0f0', minHeight: '100vh' }}>
      <h1 style={{ textAlign: 'center', marginBottom: '20px' }}>Color Palette Generator</h1>
      <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '20px' }}>
        <button onClick={handleGeneratePalette} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '5px', marginRight: '10px' }}>Generate Palette</button>
        <button onClick={handleSavePalette} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer', backgroundColor: '#2196F3', color: 'white', border: 'none', borderRadius: '5px' }}>Save Palette</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap' }}>
        {colors.map((color, index) => (
          <div
            key={index}
            style={{
              width: '100px',
              height: '100px',
              backgroundColor: color,
              margin: '10px',
              borderRadius: '5px',
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              color: 'white',
              fontSize: '14px',
              fontWeight: 'bold',
              cursor: 'pointer',
              boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)'
            }}
            onClick={() => {
              navigator.clipboard.writeText(color);
              alert('Color copied to clipboard!');
            }}
          >
            {color}
          </div>
        ))}
      </div>
      <h2 style={{marginTop: '20px', textAlign: 'center'}}>Saved Palettes</h2>
      <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
        {savedPalettes.map(palette => (
          <div key={palette.id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px', borderRadius: '5px', width: '200px' }}>
            <div style={{ display: 'flex', flexWrap: 'wrap' }}>
              {palette.colors.map((color, index) => (
                <div key={index} style={{ width: '30px', height: '30px', backgroundColor: color, margin: '2px', borderRadius: '3px' }}></div>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Key changes include:

  • Importing useEffect: We import useEffect to handle side effects, such as loading and saving data to local storage.
  • savedPalettes state: We add a savedPalettes state to store the saved palettes.
  • useEffect hook for loading: When the component mounts, we load saved palettes from local storage using useEffect.
  • useEffect hook for saving: We use another useEffect to save the savedPalettes to local storage whenever they change.
  • handleSavePalette function: This function creates a new palette object with a unique ID and the current colors and adds it to the savedPalettes.
  • Rendering saved palettes: We render the saved palettes below the color boxes, displaying each palette’s colors.

Now, when you generate a palette and click the “Save Palette” button, the current colors will be saved to local storage and displayed below. Refreshing the page will retain the saved palettes.

Displaying Color Names

Next, let’s add the ability to display the color name. This can be achieved using a library like colornames. First, install the library:

npm install colornames

Then, modify your pages/index.js file to include color names:


// pages/index.js
import { useState, useEffect } from 'react';
import colornames from 'colornames';

function getRandomHexColor() {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
}

export default function Home() {
  const [colors, setColors] = useState([
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
    getRandomHexColor(),
  ]);
  const [savedPalettes, setSavedPalettes] = useState([]);

  useEffect(() => {
    const storedPalettes = localStorage.getItem('savedPalettes');
    if (storedPalettes) {
      setSavedPalettes(JSON.parse(storedPalettes));
    }
  }, []);

  useEffect(() => {
    localStorage.setItem('savedPalettes', JSON.stringify(savedPalettes));
  }, [savedPalettes]);

  const handleGeneratePalette = () => {
    setColors(Array(5).fill(null).map(getRandomHexColor));
  };

  const handleSavePalette = () => {
    const newPalette = { id: Date.now(), colors: colors };
    setSavedPalettes([...savedPalettes, newPalette]);
  };

  return (
    <div style={{ fontFamily: 'sans-serif', padding: '20px', backgroundColor: '#f0f0f0', minHeight: '100vh' }}>
      <h1 style={{ textAlign: 'center', marginBottom: '20px' }}>Color Palette Generator</h1>
      <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '20px' }}>
        <button onClick={handleGeneratePalette} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer', backgroundColor: '#4CAF50', color: 'white', border: 'none', borderRadius: '5px', marginRight: '10px' }}>Generate Palette</button>
        <button onClick={handleSavePalette} style={{ padding: '10px 20px', fontSize: '16px', cursor: 'pointer', backgroundColor: '#2196F3', color: 'white', border: 'none', borderRadius: '5px' }}>Save Palette</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap' }}>
        {colors.map((color, index) => (
          <div
            key={index}
            style={{
              width: '100px',
              height: '100px',
              backgroundColor: color,
              margin: '10px',
              borderRadius: '5px',
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              color: 'white',
              fontSize: '14px',
              fontWeight: 'bold',
              cursor: 'pointer',
              boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)'
            }}
            onClick={() => {
              navigator.clipboard.writeText(color);
              alert('Color copied to clipboard!');
            }}
          >
            {color}
            <br />
            <span style={{ fontSize: '12px', fontWeight: 'normal' }}>{colornames(color) || 'Unknown'}</span>
          </div>
        ))}
      </div>
      <h2 style={{marginTop: '20px', textAlign: 'center'}}>Saved Palettes</h2>
      <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
        {savedPalettes.map(palette => (
          <div key={palette.id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px', borderRadius: '5px', width: '200px' }}>
            <div style={{ display: 'flex', flexWrap: 'wrap' }}>
              {palette.colors.map((color, index) => (
                <div key={index} style={{ width: '30px', height: '30px', backgroundColor: color, margin: '2px', borderRadius: '3px' }}></div>
              ))}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

Key changes include:

  • Importing colornames: We import the colornames library.
  • Displaying color names: Inside the color box, we use colornames(color) || 'Unknown' to display the color name. If the library can’t find a name, it will show “Unknown.”

Now, the color palette generator will display the color names below each color box, enhancing usability and providing more information about the generated colors.

Common Mistakes and Troubleshooting

As you build your color palette generator, you may encounter some common issues. Here are some troubleshooting tips:

  • Incorrect import paths: Double-check your import statements. Make sure you’re importing the correct modules from the correct locations. For example, if you’re using a library, ensure you’ve installed it using npm or yarn and that the import path is correct.
  • State not updating: If your UI isn’t updating after a state change, make sure you’re using the correct state update function (e.g., setColors). Also, ensure you’re not directly mutating the state; instead, create a new array or object with the updated values.
  • CSS issues: If your styles aren’t applying, verify that you’ve correctly linked your CSS files or that you’re using inline styles correctly. Check for typos in your CSS properties and values. If you’re using a CSS-in-JS solution, ensure it’s configured correctly.
  • Local storage errors: When working with local storage, make sure your browser is allowing local storage access. Also, be mindful of the data types you’re storing and retrieving. Local storage stores data as strings, so you’ll need to parse JSON objects using JSON.parse() and stringify them using JSON.stringify().
  • Library conflicts: If you’re using multiple libraries, ensure they don’t have conflicting dependencies or naming conventions. Check the documentation for each library to see if there are any known compatibility issues.
  • Console errors: Always check the browser’s console for error messages. These messages often provide valuable clues about what’s going wrong.

If you get stuck, don’t hesitate to consult the documentation for Next.js, React, and any libraries you’re using. Search online for specific error messages or issues you’re facing. Stack Overflow and other developer communities are great resources for finding solutions and getting help.

Key Takeaways

  • Next.js Fundamentals: You’ve learned how to create a Next.js project, set up a basic page, and use state to manage data.
  • Component Structure: You’ve built a React component that generates a color palette, demonstrating how to structure a UI with JSX.
  • Event Handling: You’ve implemented event handling to respond to user interactions, such as generating new palettes and saving palettes.
  • Styling with Inline Styles: You’ve learned how to style your application using inline styles, providing a basic understanding of styling in React.
  • Local Storage Integration: You’ve integrated local storage to persist user data, enabling features like saving and retrieving color palettes.
  • Working with External Libraries: You’ve learned how to integrate an external library (colornames) to enhance your application’s functionality.

FAQ

Here are some frequently asked questions about building a color palette generator:

  1. Can I customize the number of colors in the palette?
    Yes, you can easily modify the code to allow users to select the number of colors they want in their palette. You could add a state variable to hold the number of colors and dynamically generate the color boxes based on that value.
  2. How can I improve the color selection?
    You can use more sophisticated color generation algorithms. Libraries like chroma.js or colorjs.io offer advanced features for generating harmonious color palettes (e.g., complementary, analogous, triadic colors). You could also add options for users to specify base colors or color harmony rules.
  3. How can I make the app responsive?
    You can use CSS media queries or a CSS framework like Bootstrap or Tailwind CSS to make the application responsive. This ensures your color palette generator looks good on various screen sizes.
  4. Can I add more features?
    Absolutely! You could add features like a color picker, the ability to adjust color brightness and saturation, or even an option to export the color palette in various formats (e.g., CSS, JSON).

This tutorial has provided a solid foundation for building a color palette generator with Next.js. You can now adapt and expand upon this project to create a more feature-rich and user-friendly tool. Experiment with different features, explore advanced styling techniques, and integrate other libraries to enhance your application. The possibilities are vast, and the knowledge you’ve gained can be applied to many other web development projects. Continue learning, and remember that building projects is the best way to solidify your skills and expand your knowledge of web development.