Introduction
A Chrome Extension is a small software program that adds extra features or functionality to your Google Chrome browser. Think of it like an “app” for your browser. Just like mobile apps enhance your phone, Chrome Extensions enhance your browser by allowing it to do more than just display websites.
Chrome Extensions can :
- Modify how a website looks (e.g. turn a white background to dark mode)
- Save things for later (notes, bookmarks, screenshots)
- Autofill forms or manage passwords
- Block ads or filter websites
- Translate text or check grammar
- Integrate third-party tools (e.g. Trello, Notion, WhatsApp)
In this blog ,we’re going to build a Dark Mode Switcher—a Chrome Extension that adds a toggle switch to your browser. When turned on, it will apply a dark mode theme to the current website you’re viewing.
How Does Chrome Extension Work?
A Chrome Extension is made up of several parts:
Components | Description |
manifest.json | The config file that tells Chrome what the extension does |
Popup | A small UI that appears when you click the extension icon |
Background script | Code that runs behind the scenes (even without the popup open) |
Content script | Code that runs directly inside web pages (to modify or interact with them) |
Prerequisites
- Node.js and npm
- React
- Typescript
Folder Structure
dark-mode-extension/
|----- public/
| |---- manifest.json
| |---- background.js
| |---- icon.png
|
|
|----- src/
| |---- App.tsx
| |---- main.tsx
| |---- index.css
|
|
|---- index.html
|---- package.json
|---- tsconfig.json
|---- vite.config.ts
Step 1 : Create a New Vite Project
To get started with building a Chrome Extension using React and TypeScript, we begin by creating a new project using Vite. Vite is a modern build tool that offers fast development and optimized builds. Using the command :
npm create vite@latest dark-mode-extension -- --template react-ts
cd dark-mode-extension
npm install
npm install --save-dev @types/chrome
Step 2 : Add Chrome Extension Files
Every Chrome Extension needs a manifest.json file, which is like a blueprint describing your extension to the browser. It tells Chrome what scripts to run, what permissions are needed, and what the extension is called. We create this file under the public/ directory.
We also add a background.js file, which will contain background scripts — these run separately from your popup UI and can listen for events like installation or tab updates. The icon file (icon.png) will be used in the Chrome toolbar and the extension listing. These files ensure your extension is recognized and can interact properly with browser tabs and content.
Create public/manifest.json and put below code into that file.
{
"manifest_version": 3,
"name": "Dark Mode Switcher",
"version": "1.0",
"description": "Toggle dark mode on any website",
"action": {
"default_popup": "index.html",
"default_icon": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
}
},
"permissions": ["scripting", "activeTab", "storage"],
"background": {
"service_worker": "background.js",
"type": "module"
},
"host_permissions": [""]
}
Create public/background.js :
chrome.runtime.onInstalled.addListener(() => {
console.log('Dark Mode Extension Installed');
});
Add an icon
Place any icon.png file into public/ folder.
Step 3 : Update tsconfig.json file
In this step, we modify tsconfig.json to include the Chrome types and configure the project to support DOM and modern JavaScript features (ESNext). This allows the TypeScript compiler to recognize chrome.* APIs without errors.
Put below code into tsconfig.json :
{
"files": [],
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "ESNext"],
"types": ["chrome"],
},
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"erasableSyntaxOnly": true
}
Step 4 : Build the React Popup
This step is writing the fundamental logic for the popup interface in React in App.tsx. We implement a basic UI with a toggle switch to turn dark mode on or off. The component employs the chrome.storage.sync API to recall the dark mode preference between sessions.
Upon toggling, the extension adds a tiny CSS block into the current tab by invoking chrome.scripting.executeScript(). It alters the appearance of the webpage by using a dark background and light foreground. The application of useEffect ensures the toggle represents the stored state upon opening the popup, giving the user a consistent experience.
Put below code into src/App.tsx :
import { useEffect, useState } from 'react';
function App() {
const [enabled, setEnabled] = useState(false);
useEffect(() => {
chrome.storage.sync.get(['darkModeEnabled'], (result) => {
setEnabled(result.darkModeEnabled || false);
});
}, []);
const toggleDarkMode = () => {
const isEnabled = !enabled;
setEnabled(isEnabled);
chrome.storage.sync.set({ darkModeEnabled: isEnabled });
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const tab = tabs[0];
if (tab.id) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: updateDarkModeStyles,
args: [isEnabled],
});
}
});
};
return (
Dark Mode
);
}
function updateDarkModeStyles(enabled: boolean) {
const styleId = '__dark_mode_styles__';
let style = document.getElementById(styleId) as HTMLStyleElement;
if (!style && enabled) {
style = document.createElement('style');
style.id = styleId;
style.textContent = `
html, body, * {
background-color: #121212 !important;
color: #ffffff !important;
border-color: #333 !important;
}
`;
document.head.appendChild(style);
} else if (style && !enabled) {
style.remove();
}
}
Step 5 : Update Vite Config
Since Vite is designed primarily for web apps, we need to configure it slightly to output a build format that works well with Chrome Extensions. In the vite.config.ts, we specify that the build output should go into a dist/ folder (which we’ll load into Chrome), and that the input file is index.html.
We also include the @vitejs/plugin-react plugin to ensure Vite can compile React JSX/TSX files. This step is critical because Chrome Extensions don’t serve files through a dev server; everything must be pre-built and loaded as static files.Put below code into vite.config.ts :
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
rollupOptions: {
input: 'index.html',
},
},
});
Step 6 : Build the Extension
npm run build
This command compiles the TypeScript and React code into plain JavaScript and bundles all necessary assets into the dist/ folder. This folder contains the final, optimized code that Chrome can run. Without this build step, Chrome wouldn’t be able to recognize or run the raw project files.
Step 7 : Load in Chrome
Now that you’ve built your extension, it’s time to see it in action inside the Chrome browser.
Chrome doesn’t install extensions directly from your code — instead, you need to load the built version manually using Developer Mode.
Follow these steps :
- Open chrome://extensions in your Chrome browser.
- Enable Developer Mode using the toggle in the top right.
- Click the “Load unpacked” button.
- Select the dist/ folder from your project (this is the build output).
Conclusion
In this blog, you learned how to build a Chrome Extension from scratch using React, TypeScript, and Vite. We created a Dark Mode Switcher that can inject custom styles into any webpage and remember your preference using Chrome’s storage.
You also learned how to :
- Set up a React project with Vite and TypeScript.
- Use the chrome API to interact with tabs and storage.
- Inject JavaScript and CSS into web pages using chrome.scripting.
- Package and load your extension into Chrome manually.