ReactJS

Build your own Chrome Extension with React

Jaykrut Kotadiya
Jaykrut KotadiyaJul 1, 2025

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:

ComponentsDescription
manifest.jsonThe config file that tells Chrome what the extension does
PopupA small UI that appears when you click the extension icon
Background scriptCode that runs behind the scenes (even without the popup open)
Content scriptCode that runs directly inside web pages (to modify or interact with them)

Prerequisites

  • Node.js and npm
  • React
  • Typescript

Folder Structure

1dark-mode-extension/
2
3  |----- public/
4  |         |---- manifest.json 
5  |         |---- background.js 
6  |         |---- icon.png 
7  |
8  |
9  |----- src/
10  |         |---- App.tsx
11  |         |---- main.tsx
12  |         |---- index.css 
13  |
14  |
15  |---- index.html
16  |---- package.json
17  |---- tsconfig.json
18  |---- 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:

1npm create vite@latest dark-mode-extension -- --template react-ts
2cd dark-mode-extension
3npm install
4npm 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.

1{
2  "manifest_version": 3,
3  "name": "Dark Mode Switcher",
4  "version": "1.0",
5  "description": "Toggle dark mode on any website",
6  "action": {
7    "default_popup": "index.html",
8    "default_icon": {
9      "16": "icon.png",
10      "48": "icon.png",
11      "128": "icon.png"
12    }
13  },
14  "permissions": ["scripting", "activeTab", "storage"],
15  "background": {
16    "service_worker": "background.js",
17    "type": "module"
18  },
19  "host_permissions": ["<all_urls>"]
20}


Create public/background.js

1chrome.runtime.onInstalled.addListener(() => {
2  console.log('Dark Mode Extension Installed');
3});

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

1{
2  "files": [],
3  "compilerOptions": {
4    "target": "ESNext",
5    "lib": ["DOM", "ESNext"],
6    "types": ["chrome"],
7  },
8  "references": [
9    { "path": "./tsconfig.app.json" },
10    { "path": "./tsconfig.node.json" }
11    
12  ],
13  "erasableSyntaxOnly": true
14}

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

1import { useEffect, useState } from 'react';
2
3function App() {
4  const [enabled, setEnabled] = useState(false);
5
6  useEffect(() => {
7    chrome.storage.sync.get(['darkModeEnabled'], (result) => {
8      setEnabled(result.darkModeEnabled || false);
9    });
10  }, []);
11
12  const toggleDarkMode = () => {
13    const isEnabled = !enabled;
14    setEnabled(isEnabled);
15    chrome.storage.sync.set({ darkModeEnabled: isEnabled });
16
17     chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
18      const tab = tabs[0];
19      if (tab.id) {
20        chrome.scripting.executeScript({
21          target: { tabId: tab.id },
22          func: updateDarkModeStyles,
23          args: [isEnabled],        
24          });
25      }
26    });
27  };
28
29  return (
30    <div style={{ padding: '1rem', width: 220 }}>
31      <h3> Dark Mode</h3>
32      <label>
33        <input type="checkbox" checked={enabled} onChange={toggleDarkMode} />
34        Enable Dark Mode
35      </label>
36    </div>
37  );
38}
39
40function updateDarkModeStyles(enabled: boolean) {
41  const styleId = '__dark_mode_styles__';
42  let style = document.getElementById(styleId) as HTMLStyleElement;
43
44  if (!style && enabled) {
45    style = document.createElement('style');
46    style.id = styleId;
47    style.textContent = `
48      html, body, * {
49        background-color: #121212 !important;
50        color: #ffffff !important;
51        border-color: #333 !important;
52      }
53    `;
54    document.head.appendChild(style);
55  } else if (style && !enabled) {
56    style.remove();
57  }
58}

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

1import { defineConfig } from 'vite';
2import react from '@vitejs/plugin-react';
3
4export default defineConfig({
5  plugins: [react()],
6  build: {
7    outDir: 'dist',
8    rollupOptions: {
9      input: 'index.html',
10    },
11  },
12});

Step 6: Build the Extension

1npm 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)

Video Link

https://www.awesomescreenshot.com/video/41500582?key=932388557601e1e741b5dcbce675ef56

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

© 2026 IGNEK. All rights reserved.

Ignek on LinkedInIgnek on InstagramIgnek on FacebookIgnek on YouTubeIgnek on X