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