# How to Set Up and Use Tailwind CSS for Styling Chrome Extension Pages

> Learn to easily set up and use Tailwind CSS for styling Chrome extension pages. This guide leverages a shared Tailwind configuration for isolated, production-ready CSS bundles.

- Repository: [JongHak Seo/chrome-extension-boilerplate-react-vite](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite)
- Tags: how-to-guide
- Published: 2026-03-05

---

**The chrome-extension-boilerplate-react-vite provides a shared Tailwind configuration automatically merged into every extension UI page via the `withUI` helper, generating isolated, production-ready CSS bundles during the Vite build process.**

Setting up Tailwind CSS for Chrome extension pages requires handling isolated contexts, per-page scoping, and Manifest V3 constraints. The `jonghakseo/chrome-extension-boilerplate-react-vite` repository implements a centralized architecture that lets you set up and use Tailwind CSS across popups, side panels, options pages, and devtools panels without configuration duplication.

## How the Tailwind CSS Architecture Works

The boilerplate distributes Tailwind through a multi-layer configuration system that keeps global design tokens consistent while allowing per-page customization.

### Global Configuration Layer

The foundation resides in [`packages/tailwindcss-config/tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/tailwindcss-config/tailwind.config.ts). This file establishes the base theme, color palette, spacing scale, and plugins shared across every extension page. When you modify this file, changes propagate to all UI components automatically.

### The withUI Helper Function

Located in [`packages/ui/lib/with-ui.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/ui/lib/with-ui.ts), the `withUI` utility performs a deep merge between the global Tailwind config and page-specific overrides. This function ensures that customizations in individual pages extend rather than replace the shared design system, preventing configuration drift.

### Per-Page Configuration

Each extension page contains a minimal [`tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/tailwind.config.ts) that imports `withUI` and defines its content paths. For example, [`pages/side-panel/tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/pages/side-panel/tailwind.config.ts) specifies which files Tailwind should scan for class names:

```typescript
import { withUI } from '@extension/ui';

export default withUI({
  content: ['index.html', 'src/**/*.tsx'],
});

```

This isolation ensures Tailwind only includes utilities actually used in that specific page, keeping CSS payloads minimal.

### CSS Entry Points and Directives

Every page imports the shared CSS entry point in its local stylesheet. In [`pages/side-panel/src/index.css`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/pages/side-panel/src/index.css):

```css
@import '@extension/ui/global.css';

```

The [`packages/ui/global.css`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/ui/global.css) file contains the standard Tailwind directives:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

```

### Vite Build Integration

During the build process, [`packages/vite-config/lib/build-content-script.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/vite-config/lib/build-content-script.ts) executes the Tailwind CLI against each page's [`index.css`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/index.css). The `withTw` flag defaults to `true`, ensuring CSS compilation happens automatically. The generated stylesheets emit to `dist/<page>/index.css` alongside the JavaScript bundles, satisfying Manifest V3's requirement for static assets.

## Configuring Tailwind for a New Extension Page

To set up Tailwind CSS when adding a new extension page:

1. Create a [`tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/tailwind.config.ts) in your new page directory that calls `withUI`:

```typescript
import { withUI } from '@extension/ui';

export default withUI({
  content: ['index.html', 'src/**/*.{tsx,html}'],
});

```

2. Import the global CSS in your page's entry stylesheet:

```css
@import '@extension/ui/global.css';

```

3. Use Tailwind utilities directly in React components:

```tsx
export default function NewPage() {
  return (
    <div className="flex flex-col gap-4 p-6 bg-slate-50">
      <h1 className="text-xl font-semibold text-gray-900">
        Extension Page Title
      </h1>
      <button className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
        Submit
      </button>
    </div>
  );
}

```

The Vite dev server watches [`tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/tailwind.config.ts) and CSS files, recompiling automatically when you run `pnpm dev`.

## Benefits of This Setup for Chrome Extensions

**Per-Page Isolation** – Each page maintains its own `content` configuration, so the popup only ships CSS for popup components, and the side panel only ships side-panel styles. This prevents the CSS bloat common in monolithic Tailwind setups.

**Shared Design System** – The global config in [`packages/tailwindcss-config/tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/tailwindcss-config/tailwind.config.ts) acts as a single source of truth for brand colors, typography, and spacing. The `withUI` helper guarantees every page inherits these tokens without manual synchronization.

**Zero Runtime Overhead** – Tailwind compiles to plain CSS during the Vite build. The extension ships static CSS files referenced in [`manifest.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/manifest.json), adhering to Manifest V3 security policies that prohibit runtime script injection or eval-based style generation.

## Summary

- The boilerplate stores global Tailwind settings in [`packages/tailwindcss-config/tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/tailwindcss-config/tailwind.config.ts).
- The `withUI` helper in [`packages/ui/lib/with-ui.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/ui/lib/with-ui.ts) merges global and page-specific configurations.
- Each page defines its own [`tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/tailwind.config.ts) pointing to local markup files for optimal CSS pruning.
- Pages import `@extension/ui/global.css` to receive Tailwind directives.
- The Vite configuration in [`packages/vite-config/lib/build-content-script.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/vite-config/lib/build-content-script.ts) automatically compiles Tailwind during both development and production builds.

## Frequently Asked Questions

### How do I customize the global Tailwind theme for all extension pages?

Modify [`packages/tailwindcss-config/tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/tailwindcss-config/tailwind.config.ts) to add custom colors, fonts, or spacing scales. Because every page's config merges with this base file through `withUI`, your theme changes apply immediately across popups, options pages, and side panels without editing individual page configurations.

### Why does each page need its own tailwind.config.ts instead of one root config?

Individual configs allow precise control over the `content` array. By scoping Tailwind's scanner to only the files used by that specific extension page—such as `pages/popup/src/**/*.tsx`—the build system generates minimal CSS bundles. This architectural choice prevents shipping styles for the side panel inside the popup bundle, keeping extension package sizes small.

### How do I add a Tailwind plugin to just one specific page?

Pass the plugin directly to `withUI` in that page's local [`tailwind.config.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/tailwind.config.ts). The deep-merge algorithm in [`packages/ui/lib/with-ui.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/ui/lib/with-ui.ts) combines page-specific plugins with the global plugin array. For example, adding a forms plugin to only the options page keeps the popup bundle lean while enhancing the options UI.

### Does this Tailwind setup work with content scripts?

The current architecture targets extension UI pages (popup, options, devtools, side-panel) through the Vite page build system. Content scripts require a different approach since they inject into arbitrary web pages, but the boilerplate's [`build-content-script.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/build-content-script.ts) infrastructure can be extended to process Tailwind for content script CSS if you configure a separate entry point and content array specific to those components.