Renderless Vue.js components that expose VueUse composable functionality through declarative template-based interfaces
—
Components for managing color schemes, dark mode, and user preferences.
Manages color scheme preferences with support for system, light, and dark modes.
/**
* Component that manages color scheme preferences
* @example
* <UseColorMode v-slot="{ mode, system, store }">
* <div>Current mode: {{ mode }} (System: {{ system }})</div>
* </UseColorMode>
*/
interface UseColorModeProps {
/** Color mode selector attribute @default 'class' */
selector?: string;
/** CSS attribute for mode application @default 'class' */
attribute?: string;
/** Default color mode @default 'auto' */
defaultValue?: BasicColorMode | string;
/** Storage key for persistence @default 'vueuse-color-scheme' */
storageKey?: string;
/** Storage interface @default localStorage */
storage?: StorageLike;
/** Emit 'auto' mode as preference @default true */
emitAuto?: boolean;
/** Transition CSS selector @default undefined */
transition?: ViewTransition | string;
/** Disable transitions @default false */
disableTransition?: boolean;
/** Storage serializer */
serializer?: Serializer<string>;
/** Available modes */
modes?: (BasicColorMode | string)[];
/** Custom mode change handler */
onChanged?: (mode: ColorMode, defaultHandler: (mode: ColorMode) => void) => void;
}
/** Slot data exposed by UseColorMode component */
interface UseColorModeReturn<T extends string = BasicColorMode> {
/** Current color mode */
mode: WritableComputedRef<T | BasicColorMode>;
/** System color mode preference */
system: ComputedRef<BasicColorMode>;
/** Store/change color mode */
store: WritableComputedRef<T | BasicColorMode>;
}
type BasicColorMode = 'light' | 'dark' | 'auto';
type ColorMode = BasicColorMode | string;
interface StorageLike {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}
interface ViewTransition {
ready: Promise<void>;
}
interface Serializer<T> {
read(value: any): T;
write(value: T): string;
}Usage Examples:
<template>
<!-- Basic color mode -->
<UseColorMode v-slot="{ mode, system }">
<div class="color-mode-demo">
<h3>Color Mode Manager</h3>
<p>Current mode: {{ mode }}</p>
<p>System preference: {{ system }}</p>
<div class="mode-controls">
<button @click="setMode('light')" :class="{ active: mode === 'light' }">
☀️ Light
</button>
<button @click="setMode('dark')" :class="{ active: mode === 'dark' }">
🌙 Dark
</button>
<button @click="setMode('auto')" :class="{ active: mode === 'auto' }">
🔄 Auto
</button>
</div>
</div>
</UseColorMode>
<!-- Custom modes -->
<UseColorMode
:modes="['light', 'dark', 'sepia', 'contrast']"
default-value="light"
v-slot="{ mode, store }"
>
<div class="custom-modes">
<h3>Custom Color Modes</h3>
<p>Active mode: {{ mode }}</p>
<div class="custom-controls">
<button
v-for="customMode in ['light', 'dark', 'sepia', 'contrast']"
:key="customMode"
@click="store = customMode"
:class="{ active: mode === customMode }"
>
{{ customMode }}
</button>
</div>
</div>
</UseColorMode>
<!-- With transitions -->
<UseColorMode
transition="view-transition"
:disable-transition="false"
v-slot="{ mode, store }"
>
<div class="transition-demo">
<h3>Mode with Transitions</h3>
<div class="theme-preview" :data-theme="mode">
<p>This content transitions smoothly</p>
<button @click="store = mode === 'light' ? 'dark' : 'light'">
Toggle Theme
</button>
</div>
</div>
</UseColorMode>
</template>
<script setup>
import { UseColorMode } from '@vueuse/components';
// This function would need to be implemented with access to the slot data
function setMode(newMode) {
// Implementation depends on how you access the slot data
}
</script>
<style>
.mode-controls, .custom-controls {
display: flex;
gap: 10px;
margin-top: 15px;
}
.mode-controls button, .custom-controls button {
padding: 8px 16px;
border: 2px solid #ddd;
border-radius: 6px;
background: white;
cursor: pointer;
transition: all 0.2s;
}
.mode-controls button.active, .custom-controls button.active {
background: #2196f3;
color: white;
border-color: #1976d2;
}
.theme-preview {
padding: 20px;
border: 2px solid #ddd;
border-radius: 8px;
transition: all 0.3s ease;
}
.theme-preview[data-theme="dark"] {
background: #333;
color: white;
border-color: #555;
}
.theme-preview[data-theme="sepia"] {
background: #f4f3e8;
color: #5c4b37;
}
.theme-preview[data-theme="contrast"] {
background: black;
color: white;
border-color: white;
}
</style>Manages dark mode state with automatic system preference detection.
/**
* Component that manages dark mode state
* @example
* <UseDark v-slot="{ isDark, toggle }">
* <div>Dark mode: {{ isDark ? 'ON' : 'OFF' }}</div>
* <button @click="toggle">Toggle</button>
* </UseDark>
*/
interface UseDarkProps {
/** CSS selector for dark mode application @default 'html' */
selector?: string;
/** CSS attribute for dark mode @default 'class' */
attribute?: string;
/** Default dark mode value @default 'auto' */
defaultValue?: boolean | 'auto';
/** Storage key for persistence @default 'vueuse-dark' */
storageKey?: string;
/** Storage interface @default localStorage */
storage?: StorageLike;
/** Value to set when dark @default 'dark' */
valueDark?: string;
/** Value to set when light @default '' */
valueLight?: string;
/** Custom handler for mode changes */
onChanged?: (dark: boolean, defaultHandler: (dark: boolean) => void, mode: string) => void;
}
/** Slot data exposed by UseDark component */
interface UseDarkReturn {
/** Whether dark mode is active */
isDark: WritableComputedRef<boolean>;
/** Toggle dark mode */
toggle: (value?: boolean) => boolean;
}Usage Examples:
<template>
<!-- Basic dark mode toggle -->
<UseDark v-slot="{ isDark, toggle }">
<div class="dark-toggle">
<div class="theme-indicator" :class="{ dark: isDark }">
{{ isDark ? '🌙 Dark Mode' : '☀️ Light Mode' }}
</div>
<button @click="toggle" class="toggle-btn">
Switch to {{ isDark ? 'Light' : 'Dark' }} Mode
</button>
</div>
</UseDark>
<!-- Custom dark mode values -->
<UseDark
value-dark="dark-theme"
value-light="light-theme"
attribute="data-theme"
v-slot="{ isDark, toggle }"
>
<div class="custom-dark">
<p>Using custom CSS attribute: data-theme="{{ isDark ? 'dark-theme' : 'light-theme' }}"</p>
<button @click="toggle">Toggle Theme</button>
</div>
</UseDark>
<!-- Dark mode with storage key -->
<UseDark
storage-key="my-app-theme"
:default-value="false"
v-slot="{ isDark, toggle }"
>
<div class="stored-dark">
<h3>Persistent Dark Mode</h3>
<p>State persisted as 'my-app-theme': {{ isDark }}</p>
<label class="switch">
<input type="checkbox" :checked="isDark" @change="toggle" />
<span class="slider"></span>
</label>
</div>
</UseDark>
</template>
<script setup>
import { UseDark } from '@vueuse/components';
</script>
<style>
.dark-toggle {
padding: 20px;
border: 2px solid #ddd;
border-radius: 8px;
text-align: center;
}
.theme-indicator {
font-size: 1.5em;
margin-bottom: 15px;
padding: 10px;
border-radius: 6px;
background: #f9f9f9;
transition: all 0.3s;
}
.theme-indicator.dark {
background: #333;
color: white;
}
.toggle-btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
background: #2196f3;
color: white;
cursor: pointer;
font-size: 16px;
}
/* Toggle switch styles */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #2196f3;
}
input:checked + .slider:before {
transform: translateX(26px);
}
</style>Tracks the system's preferred color scheme.
/**
* Component that tracks system preferred color scheme
* @example
* <UsePreferredColorScheme v-slot="{ colorScheme }">
* <div>System prefers: {{ colorScheme }}</div>
* </UsePreferredColorScheme>
*/
interface UsePreferredColorSchemeProps {
/** Window object @default defaultWindow */
window?: Window;
}
/** Slot data exposed by UsePreferredColorScheme component */
interface UsePreferredColorSchemeReturn {
/** System color scheme preference */
colorScheme: Ref<'light' | 'dark' | 'no-preference'>;
}Usage Examples:
<template>
<!-- System color scheme detection -->
<UsePreferredColorScheme v-slot="{ colorScheme }">
<div class="system-scheme">
<h3>System Color Scheme</h3>
<div class="scheme-display" :class="colorScheme">
<span class="scheme-icon">
{{ colorScheme === 'dark' ? '🌙' : colorScheme === 'light' ? '☀️' : '❓' }}
</span>
<span class="scheme-text">{{ colorScheme }}</span>
</div>
<p class="hint">Change your system theme to see this update</p>
</div>
</UsePreferredColorScheme>
<!-- Auto-theme based on system -->
<UsePreferredColorScheme v-slot="{ colorScheme }">
<div class="auto-theme" :data-theme="colorScheme">
<h3>Auto-themed Content</h3>
<p>This content automatically adapts to your system theme preference.</p>
<p>Current scheme: {{ colorScheme }}</p>
</div>
</UsePreferredColorScheme>
</template>
<script setup>
import { UsePreferredColorScheme } from '@vueuse/components';
</script>
<style>
.system-scheme {
border: 2px solid #ddd;
border-radius: 8px;
padding: 20px;
text-align: center;
}
.scheme-display {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
font-size: 1.5em;
padding: 15px;
border-radius: 6px;
margin: 15px 0;
background: #f5f5f5;
text-transform: capitalize;
}
.scheme-display.dark {
background: #333;
color: white;
}
.scheme-display.light {
background: #fff;
color: #333;
border: 1px solid #ddd;
}
.scheme-display.no-preference {
background: #e0e0e0;
color: #666;
}
.auto-theme {
padding: 20px;
border-radius: 8px;
transition: all 0.3s;
border: 2px solid #ddd;
}
.auto-theme[data-theme="dark"] {
background: #1a1a1a;
color: white;
border-color: #333;
}
.auto-theme[data-theme="light"] {
background: white;
color: #333;
border-color: #ddd;
}
.hint {
font-size: 0.9em;
color: #666;
font-style: italic;
margin-top: 10px;
}
</style>Tracks whether the user prefers dark mode.
/**
* Component that tracks dark mode preference
* @example
* <UsePreferredDark v-slot="{ prefersDark }">
* <div>Prefers dark: {{ prefersDark ? 'Yes' : 'No' }}</div>
* </UsePreferredDark>
*/
interface UsePreferredDarkProps {
/** Window object @default defaultWindow */
window?: Window;
}
/** Slot data exposed by UsePreferredDark component */
interface UsePreferredDarkReturn {
/** Whether user prefers dark mode */
prefersDark: Ref<boolean>;
}Usage Examples:
<template>
<!-- Dark preference detection -->
<UsePreferredDark v-slot="{ prefersDark }">
<div class="dark-preference" :class="{ 'prefers-dark': prefersDark }">
<h3>Dark Mode Preference</h3>
<div class="preference-indicator">
<span class="icon">{{ prefersDark ? '🌙' : '☀️' }}</span>
<span class="text">
Your system {{ prefersDark ? 'prefers dark mode' : 'prefers light mode' }}
</span>
</div>
</div>
</UsePreferredDark>
<!-- Conditional rendering based on preference -->
<UsePreferredDark v-slot="{ prefersDark }">
<div class="conditional-content">
<h3>Smart Default Theme</h3>
<div v-if="prefersDark" class="dark-content">
🌙 Dark mode content and styling
</div>
<div v-else class="light-content">
☀️ Light mode content and styling
</div>
</div>
</UsePreferredDark>
</template>
<script setup>
import { UsePreferredDark } from '@vueuse/components';
</script>
<style>
.dark-preference {
padding: 20px;
border-radius: 8px;
border: 2px solid #ddd;
transition: all 0.3s;
}
.dark-preference.prefers-dark {
background: #1e1e1e;
color: white;
border-color: #333;
}
.preference-indicator {
display: flex;
align-items: center;
gap: 10px;
font-size: 1.2em;
}
.dark-content, .light-content {
padding: 15px;
border-radius: 6px;
text-align: center;
font-size: 1.1em;
}
.dark-content {
background: #2c2c2c;
color: white;
}
.light-content {
background: #f9f9f9;
color: #333;
border: 1px solid #ddd;
}
</style>Tracks user's preferred languages from browser settings.
/**
* Component that tracks user's preferred languages
* @example
* <UsePreferredLanguages v-slot="{ languages }">
* <div>Languages: {{ languages.join(', ') }}</div>
* </UsePreferredLanguages>
*/
interface UsePreferredLanguagesProps {
/** Window object @default defaultWindow */
window?: Window;
}
/** Slot data exposed by UsePreferredLanguages component */
interface UsePreferredLanguagesReturn {
/** Array of preferred language codes */
languages: Ref<readonly string[]>;
}Usage Examples:
<template>
<!-- Language preference display -->
<UsePreferredLanguages v-slot="{ languages }">
<div class="languages-info">
<h3>Preferred Languages</h3>
<div class="languages-list">
<div
v-for="(lang, index) in languages"
:key="lang"
class="language-item"
:class="{ primary: index === 0 }"
>
<span class="flag">{{ getFlagEmoji(lang) }}</span>
<span class="code">{{ lang }}</span>
<span class="name">{{ getLanguageName(lang) }}</span>
<span v-if="index === 0" class="primary-label">Primary</span>
</div>
</div>
</div>
</UsePreferredLanguages>
<!-- Internationalization helper -->
<UsePreferredLanguages v-slot="{ languages }">
<div class="i18n-helper">
<h3>Internationalization</h3>
<p>Primary language: {{ languages[0] }}</p>
<p>Supported by app: {{ isLanguageSupported(languages[0]) ? 'Yes' : 'No' }}</p>
<div v-if="!isLanguageSupported(languages[0])" class="fallback">
<p>Falling back to: {{ getFallbackLanguage(languages) }}</p>
</div>
</div>
</UsePreferredLanguages>
</template>
<script setup>
import { UsePreferredLanguages } from '@vueuse/components';
const supportedLanguages = ['en', 'es', 'fr', 'de', 'it', 'pt', 'ja', 'ko', 'zh'];
function getFlagEmoji(lang) {
const flags = {
'en': '🇺🇸', 'es': '🇪🇸', 'fr': '🇫🇷', 'de': '🇩🇪', 'it': '🇮🇹',
'pt': '🇵🇹', 'ja': '🇯🇵', 'ko': '🇰🇷', 'zh': '🇨🇳'
};
return flags[lang.split('-')[0]] || '🌐';
}
function getLanguageName(lang) {
const names = {
'en': 'English', 'es': 'Español', 'fr': 'Français', 'de': 'Deutsch',
'it': 'Italiano', 'pt': 'Português', 'ja': '日本語', 'ko': '한국어', 'zh': '中文'
};
return names[lang.split('-')[0]] || lang;
}
function isLanguageSupported(lang) {
return supportedLanguages.includes(lang.split('-')[0]);
}
function getFallbackLanguage(languages) {
const supported = languages.find(lang => isLanguageSupported(lang));
return supported || 'en';
}
</script>
<style>
.languages-list {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 15px;
}
.language-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
background: #f9f9f9;
}
.language-item.primary {
background: #e3f2fd;
border-color: #2196f3;
}
.flag {
font-size: 1.2em;
}
.code {
font-family: monospace;
font-weight: bold;
min-width: 50px;
}
.name {
flex: 1;
}
.primary-label {
background: #2196f3;
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.8em;
}
.fallback {
margin-top: 10px;
padding: 10px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
}
</style>/**
* Component that tracks preferred contrast setting
*/
interface UsePreferredContrastReturn {
contrast: Ref<'no-preference' | 'high' | 'low'>;
}/**
* Component that tracks reduced motion preference
*/
interface UsePreferredReducedMotionReturn {
reducedMotion: Ref<'no-preference' | 'reduce'>;
}/**
* Component that tracks reduced transparency preference
*/
interface UsePreferredReducedTransparencyReturn {
reducedTransparency: Ref<'no-preference' | 'reduce'>;
}/** Common types used across theme and preference components */
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);
interface RenderableComponent {
/** The element that the component should be rendered as @default 'div' */
as?: object | string;
}
/** Color mode types */
type BasicColorMode = 'light' | 'dark' | 'auto';
type ColorMode = BasicColorMode | string;
/** Preference types */
type ContrastPreference = 'no-preference' | 'high' | 'low';
type ReducedMotionPreference = 'no-preference' | 'reduce';
type ReducedTransparencyPreference = 'no-preference' | 'reduce';
/** Storage interface */
interface StorageLike {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}Install with Tessl CLI
npx tessl i tessl/npm-vueuse--components