npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue-multiple-themes

v7.0.0

Published

Vue 3 multi-theme engine: CSS custom properties, TailwindCSS opacity modifiers, white-label brand contexts, composable API

Readme

vue-multiple-themes

Dynamic multi-theme support for Vue 3 — CSS custom properties, TailwindCSS (with full opacity modifier support), WCAG contrast utilities, white-label brand contexts, and a reactive composable API.

npm version npm downloads License: MIT TypeScript Vue 3 TailwindCSS Zero Dependencies Tests


Why vue-multiple-themes?

Most Vue theming solutions only handle light/dark toggling. vue-multiple-themes is a complete multi-theme engine for production applications.

Comparison with Alternatives

| Feature | vue-multiple-themes | @vueuse useColorMode | nuxt-color-mode | |---|:---:|:---:|:---:| | Multiple themes (3+) | ✅ Unlimited | ⚠️ Manual | ⚠️ Light/Dark only | | TailwindCSS v3 & v4 plugin | ✅ Built-in | ❌ | ❌ | | Opacity modifiers (bg-primary/50) | ✅ | ❌ | ❌ | | WCAG contrast utilities | ✅ 5+ functions | ❌ | ❌ | | Theme generation from 1 color | ✅ | ❌ | ❌ | | White-label / namespace | ✅ createBrandContext() | ❌ | ❌ | | Color utility library | ✅ 20+ functions | ❌ | ❌ | | Preset themes | ✅ 7 included | ❌ | ❌ | | System preference | ✅ | ✅ | ✅ | | TypeScript | ✅ Full | ✅ | ✅ | | Zero dependencies | ✅ | ❌ | ❌ |

📖 Full documentation →


Development

Prerequisites: pnpm v9+ and Node.js v18+

# Install all workspace dependencies
pnpm install

# Run the interactive playground (Vite dev server → http://localhost:5173)
pnpm dev

# Build the library
pnpm build

# Build the playground for production (deployed to GitHub Pages)
pnpm build:playground

Features

  • Vue 3 Optimized — leverage the latest Composition API and <script setup>
  • TypeScript — full type definitions included
  • CSS custom properties — semantic --vmt-* variables injected automatically
  • TailwindCSS pluginbg-vmt-primary, text-vmt-foreground, etc., with full opacity modifier support (bg-vmt-primary/50)
  • Tailwind v3 & v4 — dedicated plugin for each major version
  • 7 preset themes — light, dark, sepia, ocean, forest, sunset, winter
  • Dynamic theme generation — create themes from a single brand color
  • Color utilities — lighten, darken, mix, contrast ratio, WCAG compliance
  • useTheme() composable — reactive, SSR-safe, localStorage-persistent, luminance-based isDark
  • System preference detection — auto-select light/dark based on OS setting
  • Bring-your-own iconsVmtIcon :as="SunIcon" forwards to any Vue icon component (lucide-vue-next, @heroicons/vue, …); no icon data shipped in the bundle
  • White-label / multi-tenant readycreateBrandContext() creates fully isolated namespaced theme engines
  • Headless componentsVmtThemePicker with keyboard nav & ARIA, VmtIcon
  • Zero runtime dependencies (only vue peer dependency)

Installation

# pnpm (recommended)
pnpm add vue-multiple-themes

# npm
npm install vue-multiple-themes

# yarn
yarn add vue-multiple-themes

Quick Start

Vue 3 — Composition API

// main.ts
import { createApp } from 'vue';
import { VueMultipleThemesPlugin } from 'vue-multiple-themes';
import App from './App.vue';

const app = createApp(App);
app.use(VueMultipleThemesPlugin, {
  defaultTheme: 'dark',
  strategy: 'attribute', // 'attribute' | 'class' | 'both'
  storage: 'localStorage', // 'localStorage' | 'sessionStorage' | 'none'
});
app.mount('#app');
<!-- App.vue -->
<script setup lang="ts">
import { useTheme, PRESET_THEMES } from 'vue-multiple-themes';

const ts = useTheme({ themes: PRESET_THEMES });
// ts.current, ts.isDark, ts.theme are reactive (auto-unwrapped)
</script>

<template>
  <button v-for="t in ts.themes" :key="t.name" @click="ts.setTheme(t.name)">
    {{ t.label }} (Active: {{ ts.current === t.name }})
  </button>
  <p>Dark mode: {{ ts.isDark }}</p>
</template>

Icons

VmtIcon is a thin forwarder — it accepts any Vue icon component via the as prop and forwards size, color, and strokeWidth. No SVG data lives in the library.

# Install any Vue icon library — e.g.
pnpm add lucide-vue-next
import { Sun, Moon, Palette } from 'lucide-vue-next'
import { VmtIcon } from 'vue-multiple-themes'
<!-- Render any icon through VmtIcon -->
<VmtIcon :as="Sun"  :size="24" color="currentColor" />
<VmtIcon :as="Moon" :size="20" :stroke-width="1.5" />

<!-- Or use the component directly — same props accepted -->
<Sun :size="24" color="currentColor" />

Assign icons to theme definitions:

import { Sun, Moon } from 'lucide-vue-next'

const themes = [
  { name: 'light', label: 'Light', icon: Sun,  colors: { ... } },
  { name: 'dark',  label: 'Dark',  icon: Moon, colors: { ... } },
]

Attach icons to the ready-made PRESET_THEMES:

import { Sun, Moon, Monitor, Coffee, Leaf, Droplets, Flame, Snowflake } from 'lucide-vue-next'
import { PRESET_THEMES } from 'vue-multiple-themes'
import type { Component } from 'vue'

const presetIcons: Record<string, Component> = {
  light: Sun, dark: Moon, system: Monitor,
  cafe: Coffee, nature: Leaf, ocean: Droplets,
  flame: Flame, nord: Snowflake,
}

const themes = PRESET_THEMES.map(t => ({ ...t, icon: presetIcons[t.name] }))

CSS Custom Properties

Themes inject dual CSS variables on the target element (default: <html>):

/* Channel format (for Tailwind opacity modifiers) */
--vmt-primary: 59 130 246;

/* Full color (for direct CSS use) */
--vmt-primary-color: rgb(59 130 246);

Use the -color suffixed variables in custom CSS:

.card {
  background: var(--vmt-background-color);
  color: var(--vmt-foreground-color);
  border: 1px solid var(--vmt-border-color);
}

/* Manual opacity using the channel format */
.overlay {
  background: rgb(var(--vmt-primary) / 0.5);
}

TailwindCSS Integration

Tailwind v3

// tailwind.config.js
import { createVmtPlugin } from 'vue-multiple-themes/tailwind';
import { PRESET_THEMES } from 'vue-multiple-themes';

export default {
  content: ['./src/**/*.{vue,ts,tsx}'],
  plugins: [
    createVmtPlugin({
      themes: PRESET_THEMES,
      strategy: 'both',
      darkThemes: ['dark'], // enables Tailwind `dark:` modifier
    }),
  ],
};

Opacity modifiers work out of the box:

<div class="bg-vmt-primary/50 text-vmt-text border-vmt-border/75">
  <span class="text-vmt-primary/80">Semi-transparent text</span>
</div>

All Tailwind utilities are available: bg-, text-, border-, ring-, divide-, placeholder-, outline-, shadow-, accent-, caret-, fill-, stroke-, gradients (from-, via-, to-), and more.

Tailwind v4

import { generateVmtCssForV4 } from 'vue-multiple-themes/tailwind-v4';
import { PRESET_THEMES } from 'vue-multiple-themes';

// Outputs @theme and @custom-variant blocks for your CSS
const css = generateVmtCssForV4({
  themes: PRESET_THEMES,
  strategy: 'both',
  darkThemes: ['dark'],
});

Preset Themes

| Name | Description | | -------- | --------------------- | | light | Clean white + indigo | | dark | Dark gray + violet | | sepia | Warm parchment browns | | ocean | Deep sea blues | | forest | Rich greens | | sunset | Warm oranges & reds | | winter | Icy blues & whites |

import { PRESET_THEMES, oceanTheme, forestTheme } from 'vue-multiple-themes';

Dynamic Theme Generation

Create light/dark theme pairs from a single brand color:

import { generateThemePair } from 'vue-multiple-themes';

const [light, dark] = generateThemePair('#6366f1'); // indigo

Generate a full color scale:

import { generateColorScale } from 'vue-multiple-themes';

const scale = generateColorScale('#6366f1', 9); // 9-step palette

Color Utilities

All utilities are SSR-safe (no DOM dependency) and tree-shakeable:

import {
  lighten,
  darken,
  mix,
  contrastRatio,
  autoContrast,
  checkContrast,
  complementary,
  triadic,
  analogous,
  normalizeToRgbChannels,
} from 'vue-multiple-themes';

lighten('#6366f1', 0.2);       // lighter hex
darken('#6366f1', 0.3);        // darker hex
mix('#ff0000', '#0000ff', 0.5); // purple blend
contrastRatio('#000', '#fff');  // 21
autoContrast('#6366f1');        // '#ffffff' or '#000000'
checkContrast('#6366f1', '#fff'); // { ratio, aa, aaa, aaLarge, aaaLarge }
normalizeToRgbChannels('#6366f1'); // '99 102 241'

API

useTheme(options)

| Option | Type | Default | Description | | ------------------------ | ---------------------------------- | -------------- | ---------------------------------- | | themes | ThemeDefinition[] | — | Available themes (required) | | defaultTheme | string | first theme | Initial theme name | | strategy | 'attribute' \| 'class' \| 'both' | 'attribute' | How theme is applied to DOM | | target | string | 'html' | Target element selector | | storage | 'localStorage' \| 'sessionStorage' \| 'none' | 'localStorage' | Where to persist the active theme | | storageKey | string | 'vmt-theme' | Storage key for persistence | | namespace | string | — | Isolate state for white-label / multi-tenant setups | | respectSystemPreference| boolean | false | Auto-select theme matching OS mode | | onThemeChange | (newTheme, oldTheme) => void | — | Callback on every theme change |

Returns:

| Property | Type | Description | | ---------------- | -------------------------------------- | ----------------------------------------------- | | current | readonly string | Active theme name (reactive) | | theme | readonly ThemeDefinition | Active theme definition (reactive) | | isDark | readonly boolean | Luminance-based dark detection (reactive) | | themes | readonly ThemeDefinition[] | All available themes | | resolvedColors | readonly Record<string, {r,g,b}> | Active theme colors as RGB objects | | setTheme() | (name: string) => void | Activate a theme by name | | nextTheme() | () => void | Advance to the next theme | | prevTheme() | () => void | Go back to the previous theme | | toggleTheme() | () => void | Toggle between first two themes |

Note: current, theme, isDark, and resolvedColors are reactive properties wrapped in reactive(). Use them directly in templates: {{ ts.current }}. To watch() them, use a getter: watch(() => ts.current, ...).

createBrandContext(options)

| Option | Type | Description | | ------------- | ------------------- | ---------------------------------------------------------------------- | | namespace | string (required) | Unique brand identifier — scopes style tag, singleton, and provide key | | + all useTheme options | | All ThemeOptions fields accepted as context defaults |

Returns: { namespace, useTheme(overrides?), BrandPlugin }


White-label & Multi-tenant

Use createBrandContext() when you need multiple independent theme engines in the same Vue app — e.g. white-label products, micro-frontends, or embeddable widgets.

Each context isolates:

  • Its injected <style> tag (id="vmt-theme-styles-<namespace>")
  • Its singleton reactive state (keyed by <namespace>:<storageKey>)
  • Its Vue provide key ("vmt:options:<namespace>")
  • Its Tailwind color namespace (bg-acme-primary vs bg-beta-primary)
import { createBrandContext } from 'vue-multiple-themes'

export const acme = createBrandContext({
  namespace:    'acme',
  storageKey:   'acme-theme',
  cssVarPrefix: '--acme-',
  themes:       acmeThemes,
  defaultTheme: 'acme-light',
  strategy:     'attribute',
})

export const beta = createBrandContext({
  namespace:    'beta',
  storageKey:   'beta-theme',
  cssVarPrefix: '--beta-',
  themes:       betaThemes,
  defaultTheme: 'beta-light',
  strategy:     'attribute',
})

// main.ts — install both plugins
app.use(acme.BrandPlugin)
app.use(beta.BrandPlugin)
<!-- Component.vue -->
<script setup lang="ts">
import { acme, beta } from './brands'

const acmeState = acme.useTheme()
const betaState = beta.useTheme()
</script>

<template>
  <button @click="acmeState.toggleTheme()">Acme: {{ acmeState.current }}</button>
  <button @click="betaState.toggleTheme()">Beta: {{ betaState.current }}</button>
</template>

Migrating from v5 to v6

Breaking Changes

  1. CSS variable format changed--vmt-primary now contains RGB channels (59 130 246) instead of hex. Use --vmt-primary-color for the full rgb() value in custom CSS.

  2. Tailwind plugin API changedcreateVmtPlugin() now requires a themes option:

    // Before (v5)
    createVmtPlugin()
    // After (v6)
    createVmtPlugin({ themes: PRESET_THEMES })
  3. persist option renamed — Use storage: 'localStorage' instead of persist: true.

  4. useTheme() return type changed — Returns a reactive() object. Properties are accessed directly (no .value needed). currentTheme/currentName renamed to theme/current.

  5. isDark uses luminance — Now calculated from background color luminance instead of name matching.

  6. Icon API changedThemeDefinition.icon is now Component (not string). VmtIcon uses :as instead of :name. No icon data is bundled — bring your own library:

    // Before (v5 — string name, non-functional)
    { name: 'light', icon: 'sun', colors: { ... } }
    // <VmtIcon name="sun" :size="24" />
    
    // After (v6 — real Vue component)
    import { Sun } from 'lucide-vue-next'
    { name: 'light', icon: Sun, colors: { ... } }
    // <VmtIcon :as="Sun" :size="24" />

Documentation

Full documentation and live demos:

https://pooyagolchian.github.io/vue-multiple-themes/

For AI / LLMs

This project includes an llms.txt file following the llmstxt.org specification, providing structured API documentation for AI assistants.


License

MIT © Pooya Golchian