I have been building UIs with Vue for years and one pattern comes up constantly, you need more than dark/light. Clients want seasonal themes, brand-specific palettes, and accessibility-compliant contrasts. I extracted all of that into a standalone, typed library: vue-multiple-themes.
What It Solves
The standard approach is toggling a .dark class on <html> and writing a wall of CSS overrides. That works for two themes. Scale to three or more and you get duplicated selectors, fragile specificity battles, and no tooling for generating accessible palettes.
vue-multiple-themes replaces that with:
-
CSS custom properties (
--vmt-*) injected at the target element: every theme is a swap of values at one cascade layer - A reactive
useTheme()composable accessible anywhere in the component tree - 7 preset themes ready to use immediately
- A TailwindCSS plugin that exposes those tokens as Tailwind utilities
- WCAG color utilities for contrast checking, mixing, and palette generation: all SSR-safe
Installation
pnpm add vue-multiple-themes
Requires Vue 2.7+ or Vue 3. Zero runtime dependencies beyond Vue itself.
Quick Start: Vue 3
Register the plugin once in main.ts:
const app = createApp(App);
app.use(VueMultipleThemesPlugin, {
defaultTheme: 'dark',
strategy: 'attribute',
persist: true,
});
app.mount('#app');
Then use useTheme() anywhere:
<script setup lang="ts">
const { currentTheme, setTheme, themes } = useTheme({ themes: PRESET_THEMES });
</script>
<template>
<button v-for="t in themes" :key="t.name" @click="setTheme(t.name)">
{{ t.label }}
</button>
</template>
CSS Custom Properties
Once a theme is active, --vmt-* variables are available on <html>. Style components against them:
.card {
background: var(--vmt-background);
color: var(--vmt-foreground);
border: 1px solid var(--vmt-border);
}
Switching themes updates every component instantly, no re-renders required.
The 7 Preset Themes
| Name | Character |
|---|---|
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 |
Dynamic Theme Generation
const { light, dark } = generateThemePair('#6366f1');
const scale = generateColorScale('#6366f1', 9);
Ideal for SaaS products where each tenant sets a brand color and the full UI adapts automatically.
TailwindCSS Integration
const { createVmtPlugin } = require('vue-multiple-themes/tailwind');
module.exports = { plugins: [createVmtPlugin()] };
<div class="bg-vmt-surface text-vmt-foreground border-vmt-border">
Themes itself automatically on switch
</div>
WCAG Utilities
Pure functions, no DOM, fully SSR-safe, tree-shakeable:
contrastRatio('#6366f1', '#ffffff'); // 4.54
autoContrast('#6366f1'); // '#ffffff'
checkContrast('#6366f1', '#ffffff');
// { ratio: 4.54, aa: true, aaa: false, aaLarge: true, aaaLarge: true }
useTheme() API
| Option | Type | Default | Description |
|---|---|---|---|
themes |
ThemeDefinition[] |
preset list | Available themes |
defaultTheme |
string |
light |
Initial theme |
strategy |
attribute / class / both |
attribute |
DOM application strategy |
persist |
boolean |
true |
Save to localStorage |
storageKey |
string |
vmt-theme |
localStorage key |
Returns: { currentTheme, currentName, themes, setTheme, nextTheme, prevTheme }
Vue 2 Support
Vue.use(VueMultipleThemesPlugin, { defaultTheme: 'light' });
GitHub ยท npm ยท Full Documentation
Top comments (0)