Ever wondered how to change the favicon color dynamically in your Next.js app? Most tutorials out there show you how to switch the favicon between two modes light and dark based on the system or user preference. That’s great, but what if you want to take it a step further?

In this tutorial, we’ll learn how to generate a custom-colored favicon using SVG and update it in real-time based on the user’s selected color — not just light or dark themes.

We’ll use

  • Next.js as our framework
  • React Context for sharing the color state
  • A little SVG + JavaScript magic to create and update the favicon dynamically

Let’s get started! 🚀

1. Set Up a New Next.js App

First things first, let’s spin up a new Next.js project.

If you don’t already have the Next.js CLI installed, no worries. You can create a new app with just one command

npx create-next-app@latest dynamic-favicon

This will prompt you with a few setup options. For this tutorial, you can go with the defaults or customize it as you like. Once it’s done, navigate into your project folder

cd dynamic-favicon

Now let’s start the development server to make sure everything is running

npm run dev

You should see the default Next.js welcome page at http://localhost:3000. 🎉

2. Install next-themes for Theme Support

To make the favicon color dynamic, we need a way to store and manage the current color across your app. While we're not doing traditional light/dark mode, we'll still use the next-themes package because it provides a simple, client-friendly way to manage theme-like state.

Install it with this command

npm install next-themes

3. Clean Up the Default Template

Next.js comes with a default template, but we don’t need all of it. Let’s clean it up to make space for our theme and custom components.

  • Open the app/page.tsx file and remove the default content. You can replace it with a simple placeholder like this,
export default function Home() {
  return (
    
      Welcome to My Themed Next.js App
    
  );
}
  • Next, remove the default code from app/layout.tsx. your file need to be like this
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    
      
        {children}
      
    
  );
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    





Next, remove the default styling from app/globals.css or modify it as per your design. your file need to be like this


@import "tailwindcss";



    Enter fullscreen mode
    


    Exit fullscreen mode
    




4. Create a Theme Context to Hold Your ColorIn this step, we will create a context to manage the primary color of your app. This context will allow us to change the theme color dynamically and ensure the color is applied consistently across the app.First, let's create the context file. In the root of your project, create a new folder called context. Inside it, create a file called theme-context.tsx. This will contain the logic for managing and accessing the primary color.Here's the code for managing the primary color using React's createContext and useState.

"use client";

import { createContext, useContext, useState, type ReactNode } from "react";

// Define the type for the ThemeContext
type ThemeContextType = {
    currentPrimaryColor: string;
    setPrimaryColor: (color: string) => void;
}

// Create the context with default values
const ThemeContext = createContext({
    currentPrimaryColor: "#000000",  // Default color black
    setPrimaryColor: () => {}  // Placeholder function
});

// Custom hook to use the theme context
export const useTheme = () => useContext(ThemeContext);

type ThemeProviderProps = {
  children: ReactNode;
}

// ThemeProvider component that manages the primary color state
export function ThemeProvider({ children }: ThemeProviderProps) {
    const [currentPrimaryColor, setCurrentPrimaryColor] = useState("#000000");

    // Function to update the primary color
    const setPrimaryColor = (color: string) => {
        setCurrentPrimaryColor(color);
    }

    return (
        
            {children}
        
    );
}




    Enter fullscreen mode
    


    Exit fullscreen mode
    




5. Add the Context to the LayoutIn this step, we’ll make the theme context available throughout the entire application. This way, any component can access and update the primary color from anywhere in your app.We’ll achieve this by wrapping the entire app in the ThemeProvider inside the RootLayout. This ensures that all child components have access to the context.First, let's modify the RootLayout (usually found in app/layout.tsx) to wrap the entire application with the ThemeProvider we just created. Here’s the updated RootLayout code.

import type { Metadata } from "next";
import "./globals.css";
import { ThemeProvider } from "@/context/theme-context";  // Import the ThemeProvider

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    
      
        {/* Wrap the entire application with the ThemeProvider */}
        
          {children}
        
      
    
  );
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




6. Add the Context to the LayoutIn this step, we will create a component that dynamically updates the favicon based on the primary color of the theme. This will help visually reflect the color change in the browser tab. The component will use the currentPrimaryColor from the theme context to update the favicon. We will generate a new favicon each time the primary color changes, and the favicon will be a simple SVG image with a color corresponding to the theme.Here’s the code for the FaviconUpdater component.


'use client';

import { useEffect, useState } from 'react';
import { useTheme } from '@/context/theme-context';

export default function FaviconUpdater() {

  const { currentPrimaryColor } = useTheme(); // Get the current primary color from context

  const getColorHex = (color: string): string => {
    const colorMap: Record = {
      black: "#000000",
      red: "#ef4444",
      orange: "#f97316",
      yellow: "#eab308",
      lime: "#84cc16",
      green: "#22c55e",
      emerald: "#10b981",
      teal: "#14b8a6",
      cyan: "#06b6d4",
      indigo: "#6366f1",
      violet: "#8b5cf6",
      purple: "#a855f7",
      fuchsia: "#d946ef",
      pink: "#ec4899"
    };

    return colorMap[color] || "#000000";  // Return hex color or black if not found
  };

  const hexColor = getColorHex(currentPrimaryColor); // Convert the primary color to its hex value

  const [faviconUrl, setFaviconUrl] = useState(null);

  useEffect(() => {
    // Create the SVG for the favicon with the primary color
    const svg = `
      
        
        
      
    `;
    const newFaviconUrl = `data:image/svg+xml;base64,${btoa(svg)}`; // Convert the SVG to a base64 string
    setFaviconUrl(newFaviconUrl); // Set the favicon URL

    // Update the link tag in the HTML to use the new favicon
    const link = document.querySelector("link[rel='icon']") as HTMLLinkElement | null;
    if (link) {
      link.href = `${newFaviconUrl}?t=${Date.now()}`;  // Append a timestamp to prevent caching
    } else {
      const newLink = document.createElement('link');
      newLink.rel = 'icon';
      newLink.type = 'image/svg+xml';
      newLink.href = `${newFaviconUrl}?t=${Date.now()}`;
      document.head.appendChild(newLink);  // Add the new favicon to the document
    }

  }, [currentPrimaryColor]);  // Re-run when the primary color changes

  if (!faviconUrl) return null;

  return ;
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




7. Add Color Buttons and See the Magic!Now that we have the theme context and dynamic favicon working, it’s time to allow users to interact with the theme by selecting different colors. In this step, we will add a dropdown menu where users can choose a color, and the app will update accordingly. We will also see the changes reflected in the favicon and other parts of the app.We will create a simple dropdown menu that lets users choose from a list of colors. Once a color is selected, we’ll update the primary color stored in the theme context, and everything that relies on this color will automatically update.Here’s the code for the Home component

'use client'
import { useTheme } from "@/context/theme-context";

export default function Home() {
  const { currentPrimaryColor, setPrimaryColor } = useTheme(); // Access the theme context

  // List of primary colors for the dropdown
  const primaryColors = [
    { name: "Black", value: "black" },
    { name: "Red", value: "red" },
    { name: "Orange", value: "orange" },
    { name: "Yellow", value: "yellow" },
    { name: "Lime", value: "lime" },
    { name: "Green", value: "green" },
    { name: "Emerald", value: "emerald" },
    { name: "Teal", value: "teal" },
    { name: "Cyan", value: "cyan" },
    { name: "Indigo", value: "indigo" },
    { name: "Violet", value: "violet" },
    { name: "Purple", value: "purple" },
    { name: "Fuchsia", value: "fuchsia" },
    { name: "Pink", value: "pink" },
  ];

  // Handle color change
  const handleChange = (e: React.ChangeEvent) => {
    setPrimaryColor(e.target.value); // Update the primary color in context
  };

  return (
    
      
        
          Select Primary Color
        
        
          {primaryColors.map((color) => (
            
              {color.name}
            
          ))}
        
      
    
  );
}




    Enter fullscreen mode
    


    Exit fullscreen mode
    




Now your app allows users to interact with the theme color, and they can see the changes in real-time — including the favicon! The dynamic nature of this feature provides a great user experience by giving them the ability to personalize their interface.You’ve now completed the setup for creating a dynamic theme system in Next.js. 🎉Source Code - code