svelte5-chrome-extension Svelte Themes

Svelte5 Chrome Extension

Chrome Extension Starter using Vite, Svelte 5, TypeScript, TailwindCSS, and DaisyUI

Chrome Extension Starter

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.


Features

  • 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.

Getting Started

Prerequisites

Before you begin, ensure you have the following installed:


Installation

  1. Clone the Repository:

    git clone https://github.com/your-username/chrome-extension-starter.git
    cd chrome-extension-starter
    
  2. Install Dependencies:

    Using npm:

    npm install
    

    Or using pnpm:

    pnpm install
    
  3. 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.

  4. Build for Production:

    npm run build
    

    The production-ready extension will be output to the dist/ directory.


Load the Extension in Chrome

  1. Open chrome://extensions in your browser.
  2. Enable Developer Mode (toggle in the top-right corner).
  3. Click Load unpacked and select the dist/ folder generated by the build process.

Project Structure

.
├── 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

Manifest Configuration

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"
  }
}

Styling with TailwindCSS and DaisyUI

  • 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>


Development Scripts

  • npm run dev: Start the development server with HMR.
  • npm run build: Build the extension for production.

    Alternative Scripts

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.sh

All 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.


Configuration

Vite Configuration (vite.config.ts)

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",
      },
    },
  },
});

Shadow DOM & CSS Isolation

This template includes a complete solution for CSS style isolation using Shadow DOM, addressing the challenges discussed in sveltejs/svelte#5869.

Why Shadow DOM?

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

Quick Start

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();

Key Features

  • 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

Usage Patterns

1. Content Script Injection

// 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
  ],
});

2. Manual Shadow DOM Control

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();

3. Custom CSS Injection

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,
});

4. Multiple Isolated Instances

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);

API Reference

createApp(Component, options)

Main utility for mounting Svelte components with optional shadow DOM.

Options:

  • target: HTMLElement or CSS selector
  • useShadowDOM: 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 }

mountInShadow(Component, options)

Lower-level API for manual shadow DOM control.

Options:

  • target: HTMLElement
  • mode: 'open' or 'closed' (default: 'open')
  • injectStyles: Auto-inject CSS (default: true)
  • styleSheets: CSS URLs to inject (default: [])

Returns: { shadowRoot, component, cleanup }

injectCSS(options)

Inject CSS into a shadow root using Constructable Stylesheets or <link> tags.

Options:

  • shadowRoot: ShadowRoot target
  • cssUrls: Array of CSS file URLs
  • inlineCss: Inline CSS string
  • adoptedStyleSheets: Use Constructable Stylesheets (default: true)

Returns: Promise<void>

Demo Component

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

Troubleshooting

Lucide Icons Not Rendering

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
});

Styles Not Applying

Problem: CSS not loading in shadow root.

Solutions:

  1. Check that your bundled CSS is accessible: getBundledCSSUrl()
  2. Verify CORS headers if loading from external sources
  3. Use cloneDocumentStyles(shadowRoot) as a fallback

Theme Not Working

Solution: Wrap your component in a themed container:

<div data-theme="dracula">
  <!-- Your component content -->
</div>

Advanced Examples

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

Security Notes

  • 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.

Resources


Reporting Issues

If you encounter any issues or bugs, please file an issue in the GitHub repository.


Support

If you find this project helpful, consider buying me a coffee!

Support on Ko-fi ❤️


License

This project is licensed under the MIT License.


Author

Trent Brew

Top categories

Loading Svelte Themes