Skip to main content
Theme management with single-selection theme switching, token alias resolution, and CSS variable generation.

Features

  • Single-selection theme switching
  • Token alias resolution via useTokens
  • Lazy theme loading (compute colors only when selected)
  • CSS variable generation via adapter pattern
  • SSR support with head integration
  • Theme cycling

Installation

import { createThemePlugin } from '@vuetify/v0'

const app = createApp(App)
app.use(createThemePlugin({
  default: 'light',
  themes: {
    light: {
      dark: false,
      colors: {
        primary: '#1976d2',
        secondary: '#424242',
      },
    },
    dark: {
      dark: true,
      colors: {
        primary: '#90caf9',
        secondary: '#ffffff',
      },
    },
  },
}))

Basic Usage

<script setup lang="ts">
import { useTheme } from '@vuetify/v0'

const theme = useTheme()

const toggleTheme = () => {
  theme.cycle(['light', 'dark'])
}
</script>

<template>
  <div>
    <p v-if="theme.isDark.value">Dark mode is active</p>
    <button @click="toggleTheme">Toggle Theme</button>
    <button @click="theme.select('light')">Light</button>
    <button @click="theme.select('dark')">Dark</button>
  </div>
</template>

API Reference

createThemePlugin()

Creates a theme plugin.
options
ThemePluginOptions
Plugin configuration
default
ID
Default theme ID to select on initialization
themes
Record<ID, ThemeRecord>
Record of themes to registerEach theme has:
  • dark: Boolean indicating if theme is dark
  • lazy: Boolean for lazy loading (compute colors only when selected)
  • colors: Record of color values or token aliases
Example:
{
  light: {
    dark: false,
    colors: {
      primary: '#1976d2',
      secondary: '#424242',
    },
  },
  dark: {
    dark: true,
    lazy: true,
    colors: {
      primary: '#90caf9',
      secondary: '#ffffff',
    },
  },
}
palette
TokenCollection
Collection of tokens to use for resolving theme colorsExample:
{
  blue: {
    500: '#3b82f6',
    600: '#2563eb',
  },
}
target
string | HTMLElement | null
Target element or selector to apply theme classes toIf null, no classes will be applied. Defaults to document.body.
adapter
ThemeAdapter
Theme adapter for CSS generation (defaults to Vuetify0ThemeAdapter)
namespace
string
default:"'v0:theme'"
The namespace for the theme context

ThemeContext

size
number
Number of registered themes
selectedId
Ref<ID | undefined>
Currently selected theme ID
colors
ComputedRef<Record<string, Colors>>
Resolved colors for all themes (or only selected if lazy)Colors are resolved by replacing token aliases with actual values.Example:
{
  light: {
    primary: '#1976d2',
    secondary: '#424242',
  },
  dark: {
    primary: '#90caf9',
    secondary: '#ffffff',
  },
}
isDark
Readonly<Ref<boolean>>
Whether the current theme is darkReturns true if current theme has dark: true, otherwise false.
select
(id: ID) => void
Select a themeExample:
theme.select('dark')
cycle
(themes?: ID[]) => void
Cycle through provided themes in orderIf no themes provided, cycles through all registered themes.Examples:
theme.cycle(['light', 'dark'])
theme.cycle() // Cycles through all themes
register
(registration: Partial<ThemeTicketInput>) => ThemeTicket
Register a new themeExample:
theme.register({
  id: 'high-contrast',
  dark: false,
  value: {
    primary: '#000000',
    secondary: '#ffffff',
  },
})
get
(id: ID) => ThemeTicket | undefined
Get a theme by ID
has
(id: ID) => boolean
Check if a theme exists

Theme Configuration

Basic Theme

themes: {
  light: {
    dark: false,
    colors: {
      primary: '#1976d2',
      secondary: '#424242',
      error: '#f44336',
      success: '#4caf50',
    },
  },
}

Theme with Token Aliases

palette: {
  blue: {
    500: '#3b82f6',
    600: '#2563eb',
  },
  gray: {
    100: '#f3f4f6',
    900: '#111827',
  },
}

themes: {
  light: {
    dark: false,
    colors: {
      primary: '{palette.blue.500}',
      background: '{palette.gray.100}',
    },
  },
  dark: {
    dark: true,
    colors: {
      primary: '{palette.blue.600}',
      background: '{palette.gray.900}',
    },
  },
}

Lazy Loading

themes: {
  light: {
    dark: false,
    colors: { /* ... */ },
  },
  dark: {
    dark: true,
    lazy: true, // Only compute colors when selected
    colors: { /* ... */ },
  },
}

Examples

Theme Switcher

<script setup lang="ts">
import { useTheme } from '@vuetify/v0'

const theme = useTheme()

const themes = [
  { id: 'light', name: 'Light' },
  { id: 'dark', name: 'Dark' },
  { id: 'high-contrast', name: 'High Contrast' },
]
</script>

<template>
  <div>
    <select @change="theme.select($event.target.value)">
      <option 
        v-for="t in themes" 
        :key="t.id"
        :value="t.id"
        :selected="theme.selectedId.value === t.id"
      >
        {{ t.name }}
      </option>
    </select>
  </div>
</template>

Theme Toggle

<script setup lang="ts">
import { useTheme } from '@vuetify/v0'

const theme = useTheme()

const toggleTheme = () => {
  theme.cycle(['light', 'dark'])
}
</script>

<template>
  <button @click="toggleTheme">
    {{ theme.isDark.value ? '🌞' : '🌙' }}
  </button>
</template>

CSS Variables

<script setup lang="ts">
import { useTheme } from '@vuetify/v0'

const theme = useTheme()
</script>

<template>
  <div class="card">
    <h2>Card Title</h2>
    <p>Card content</p>
  </div>
</template>

<style>
.card {
  background: var(--v0-background);
  color: var(--v0-text);
  border: 1px solid var(--v0-border);
}
</style>

Dynamic Theme Registration

<script setup lang="ts">
import { useTheme } from '@vuetify/v0'
import { ref } from 'vue'

const theme = useTheme()

const customColors = ref({
  primary: '#ff0000',
  secondary: '#00ff00',
})

const createCustomTheme = () => {
  theme.register({
    id: 'custom',
    dark: false,
    value: customColors.value,
  })
  theme.select('custom')
}
</script>

<template>
  <div>
    <input v-model="customColors.primary" type="color" />
    <input v-model="customColors.secondary" type="color" />
    <button @click="createCustomTheme">Apply Custom Theme</button>
  </div>
</template>

Persistent Theme Selection

<script setup lang="ts">
import { useTheme, useStorage } from '@vuetify/v0'
import { watch, onMounted } from 'vue'

const theme = useTheme()
const storage = useStorage()
const savedTheme = storage.get('theme', 'light')

// Restore saved theme
onMounted(() => {
  if (savedTheme.value) {
    theme.select(savedTheme.value)
  }
})

// Save theme changes
watch(() => theme.selectedId.value, (newTheme) => {
  if (newTheme) {
    savedTheme.value = newTheme
  }
})
</script>

CSS Variable Naming

By default, theme colors are injected as CSS variables with the --v0- prefix:
/* Light theme */
[data-theme="light"] {
  --v0-primary: #1976d2;
  --v0-secondary: #424242;
  color-scheme: light;
}

/* Dark theme */
[data-theme="dark"] {
  --v0-primary: #90caf9;
  --v0-secondary: #ffffff;
  color-scheme: dark;
}
Use these variables in your CSS:
.button {
  background: var(--v0-primary);
  color: var(--v0-on-primary);
}

.card {
  background: var(--v0-surface);
  border: 1px solid var(--v0-border);
}

Advanced Usage

Custom Adapter

import type { ThemeAdapter } from '@vuetify/v0'

class TailwindThemeAdapter implements ThemeAdapter {
  prefix = 'tw'
  stylesheetId = 'tw-theme-styles'
  
  setup(app, context, target) {
    // Custom setup logic
  }
  
  update(theme, colors, isDark) {
    // Custom update logic
  }
  
  generate(colors, isDark) {
    // Generate Tailwind-compatible CSS
    return Object.entries(colors)
      .map(([name, colorMap]) => `
        [data-theme="${name}"] {
          ${Object.entries(colorMap)
            .map(([key, value]) => `--tw-${key}: ${value};`)
            .join('\n          ')}
          color-scheme: ${isDark ? 'dark' : 'light'};
        }
      `)
      .join('\n')
  }
}

app.use(createThemePlugin({
  adapter: new TailwindThemeAdapter(),
  themes: { /* ... */ },
}))

Multiple Theme Contexts

import { createThemeContext } from '@vuetify/v0'

const [useAppTheme, provideAppTheme] = createThemeContext({
  namespace: 'app:theme',
  themes: { /* ... */ },
})

const [useEditorTheme, provideEditorTheme] = createThemeContext({
  namespace: 'editor:theme',
  themes: { /* ... */ },
})

// In root component
provideAppTheme()

// In editor component
provideEditorTheme()

Generated Plugin composable pages successfully

Build docs developers (and LLMs) love