A modern Chrome extension starter template powered by Vite, Svelte 5, TypeScript, TailwindCSS, and DaisyUI. This template is designed for rapid development of MV3 (Manifest Version 3) extensions with a focus on performance, modularity, and modern developer tools.
- Vite: Lightning-fast bundler for modern web development.
- Svelte 5: Minimalistic framework for building user interfaces.
- TypeScript: Static typing for better code quality and maintainability.
- TailwindCSS: Utility-first CSS framework for rapid UI development.
- DaisyUI: TailwindCSS-based components for faster styling.
- Chrome Manifest V3: Secure and powerful API for Chrome extensions.
Before you begin, ensure you have the following installed:
- Node.js (v16 or higher)
- npm or pnpm (v7 or higher)
- Google Chrome
-
Clone the Repository:
git clone https://github.com/your-username/chrome-extension-starter.git cd chrome-extension-starter -
Install Dependencies:
Using npm:
npm install
Or using pnpm:
pnpm install
-
Run Development Server:
npm run dev
This will start the Vite dev server for live reloading and hot module replacement (HMR). The extension will be pre-rendered for Chrome MV3 development.
-
Build for Production:
npm run build
The production-ready extension will be output to the
dist/directory.
- Open
chrome://extensionsin your browser. - Enable Developer Mode (toggle in the top-right corner).
- Click Load unpacked and select the
dist/folder generated by the build process.
.
├── public/ # Static assets (manifest.json, icons)
├── src/
│ ├── background/ # Background scripts
│ ├── content/ # Content scripts for injecting into web pages
│ ├── popup/ # Popup UI components
│ ├── lib/ # Reusable components and utilities
│ ├── options/ # Options page components
│ ├── styles/ # TailwindCSS styles
│ ├── types/ # TypeScript declarations
│ └── main.ts # Entry point for the application
├── tailwind.config.js # TailwindCSS configuration
├── tsconfig.json # TypeScript configuration
├── vite.config.ts # Vite configuration
├── postcss.config.js # PostCSS plugins (for TailwindCSS)
└── package.json # Project dependencies and scripts
The manifest.json file is located in the public/ directory and defines the Chrome extension’s permissions and entry points.
Key Settings:
- Permissions: Add only the permissions you need to maintain user privacy.
- Background Service Worker: Configured using Vite for background tasks.
- Content Scripts: Enable interaction with web pages.
{
"manifest_version": 3,
"name": "Chrome Extension Starter",
"version": "0.0.1",
"description": "A modern Chrome extension template with Svelte, Vite, TypeScript, TailwindCSS, and DaisyUI.",
"action": {
"default_popup": "popup/index.html",
"default_icon": "icons/icon-128.png"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content/index.js"]
}
],
"permissions": ["storage", "tabs"],
"icons": {
"16": "icons/icon-16.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
}- TailwindCSS: Highly customizable utility classes for rapid UI design.
- DaisyUI: Prebuilt Tailwind components for a polished design.
Customizing Tailwind:
Edit the tailwind.config.js file to add your themes, colors, or plugins.
module.exports = {
content: ["./src/**/*.{html,js,svelte,ts}"],
theme: {
extend: {},
},
plugins: [require("daisyui")],
};DaisyUI Example:
<script>
let count = $state(0);
</script>
<div class="p-4 bg-base-200">
<button class="btn btn-primary" onclick={() => count++}>
Increment: {count}
</button>
</div>npm run dev: Start the development server with HMR.npm run build: Build the extension for production.
If you don't have just installed, you can use the shell scripts in the scripts/ directory:
# Install dependencies
./scripts/install.sh
# Start development server
./scripts/dev.sh
# Build for production
./scripts/build.sh
# Clean build artifacts
./scripts/clean.sh
# Show project status
./scripts/status.sh
# Prepare for Chrome Web Store
./scripts/prepare-publish.sh
# Quick test (rebuild + open Chrome)
./scripts/test.sh
# Show all available scripts
./scripts/help.shAll scripts include:
- ✅ Colored output with status messages
- ✅ Error handling and validation
- ✅ Help text and usage instructions
- ✅ Consistent behavior across platforms
Note: Scripts require chmod +x scripts/*.sh to be executable.
The project uses Vite for bundling. Customizations can be added in vite.config.ts.
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
export default defineConfig({
plugins: [svelte()],
build: {
rollupOptions: {
input: {
popup: "./src/popup/index.html",
background: "./src/background/index.ts",
content: "./src/content/index.ts",
},
},
},
});This template includes a complete solution for CSS style isolation using Shadow DOM, addressing the challenges discussed in sveltejs/svelte#5869.
When building Chrome extensions that inject UI into web pages (content scripts), you need style isolation to prevent:
- Host page CSS from breaking your extension UI
- Your extension CSS from breaking the host page
- Class name conflicts between your code and the page
import { createApp } from './lib/utils/createApp';
import MyComponent from './lib/components/MyComponent.svelte';
// Mount with shadow DOM and automatic CSS injection
const app = createApp(MyComponent, {
target: '#app',
useShadowDOM: true,
injectStyles: true, // Auto-injects Tailwind, DaisyUI, and Lucide icons
});
// Cleanup when done
app.cleanup();- Automatic CSS Injection: All styles (Tailwind, DaisyUI, component styles) are automatically injected into the shadow root
- Lucide Icons Support: Icons render perfectly inside shadow DOM
- Modern API: Uses Constructable Stylesheets for optimal performance
- Fallback Support: Automatically falls back to
<link>tags for older browsers - Zero Configuration: Works out of the box with your existing components
// content.ts - Inject your extension UI into any webpage
import { createApp } from '../lib/utils/createApp';
import Widget from '../lib/components/Widget.svelte';
// Create isolated container
const container = document.createElement('div');
container.id = 'my-extension-root';
container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 999999;
`;
document.body.appendChild(container);
// Mount with complete CSS isolation
const app = createApp(Widget, {
target: container,
useShadowDOM: true,
injectStyles: true,
cssUrls: [
chrome.runtime.getURL('assets/main.css'), // Your bundled CSS
],
});import { mountInShadow } from './lib/utils/shadowDOM';
import MyComponent from './lib/components/MyComponent.svelte';
const result = mountInShadow(MyComponent, {
target: document.getElementById('app'),
mode: 'open',
injectStyles: true,
});
// Access shadow root for advanced use cases
console.log('Shadow root:', result.shadowRoot);
result.cleanup();import { injectCSS, getBundledCSSUrl } from './lib/utils/cssInjector';
const shadowRoot = container.attachShadow({ mode: 'open' });
// Inject CSS with Constructable Stylesheets (fastest)
await injectCSS({
shadowRoot,
cssUrls: [getBundledCSSUrl()],
adoptedStyleSheets: true,
});import { createApps, cleanupApps } from './lib/utils/createApp';
const apps = createApps(Widget, [
{ target: '#widget1', useShadowDOM: true, injectStyles: true },
{ target: '#widget2', useShadowDOM: true, injectStyles: true },
{ target: '#widget3', useShadowDOM: true, injectStyles: true },
]);
// Cleanup all at once
cleanupApps(apps);Main utility for mounting Svelte components with optional shadow DOM.
Options:
target: HTMLElement or CSS selectoruseShadowDOM: Enable shadow DOM (default:false)shadowMode:'open'or'closed'(default:'open')injectStyles: Auto-inject CSS (default:true)cssUrls: Additional CSS files to inject (default:[])props: Component props (default:{})
Returns: { component, shadowRoot?, cleanup }
Lower-level API for manual shadow DOM control.
Options:
target: HTMLElementmode:'open'or'closed'(default:'open')injectStyles: Auto-inject CSS (default:true)styleSheets: CSS URLs to inject (default:[])
Returns: { shadowRoot, component, cleanup }
Inject CSS into a shadow root using Constructable Stylesheets or <link> tags.
Options:
shadowRoot: ShadowRoot targetcssUrls: Array of CSS file URLsinlineCss: Inline CSS stringadoptedStyleSheets: Use Constructable Stylesheets (default:true)
Returns: Promise<void>
Check out ShadowDOMDemo.svelte for a complete working example that demonstrates:
- Shadow DOM isolation
- Lucide icons rendering
- Interactive components
- Tailwind utilities
- DaisyUI themes
- Svelte 5 reactivity
Solution: Ensure injectStyles: true is enabled. The utility automatically injects all necessary CSS including Lucide icon definitions.
const app = createApp(Component, {
target: '#app',
useShadowDOM: true,
injectStyles: true, // ← This is critical
});Problem: CSS not loading in shadow root.
Solutions:
- Check that your bundled CSS is accessible:
getBundledCSSUrl() - Verify CORS headers if loading from external sources
- Use
cloneDocumentStyles(shadowRoot)as a fallback
Solution: Wrap your component in a themed container:
<div data-theme="dracula">
<!-- Your component content -->
</div>See shadowDOMExample.ts for 7 complete examples including:
- Basic shadow DOM mounting
- Manual shadow root creation
- Content script injection patterns
- Advanced CSS injection techniques
- Multiple instance management
- Real-world production patterns
- Minimal Permissions: Only request permissions that are absolutely necessary.
- Static Asset Validation: Ensure all static assets (icons, scripts) are valid and trusted.
- Content Script Isolation: Use content scripts judiciously to avoid conflicts with the web page.
- Svelte Documentation
- Vite Documentation
- Chrome Extension Docs
- TailwindCSS Documentation
- DaisyUI Documentation
If you encounter any issues or bugs, please file an issue in the GitHub repository.
If you find this project helpful, consider buying me a coffee!
This project is licensed under the MIT License.
Trent Brew
- Ko-fi: ko-fi.com/trentbrew
- GitHub: @trentbrew
