CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vueuse--components

Renderless Vue.js components that expose VueUse composable functionality through declarative template-based interfaces

Pending
Overview
Eval results
Files

theme-preferences.mddocs/

Theme and Preferences

Components for managing color schemes, dark mode, and user preferences.

Capabilities

UseColorMode Component

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>

UseDark Component

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>

UsePreferredColorScheme Component

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>

UsePreferredDark Component

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>

UsePreferredLanguages Component

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>

Additional Preference Components

UsePreferredContrast Component

/**
 * Component that tracks preferred contrast setting
 */
interface UsePreferredContrastReturn {
  contrast: Ref<'no-preference' | 'high' | 'low'>;
}

UsePreferredReducedMotion Component

/**
 * Component that tracks reduced motion preference
 */
interface UsePreferredReducedMotionReturn {
  reducedMotion: Ref<'no-preference' | 'reduce'>;
}

UsePreferredReducedTransparency Component

/**
 * Component that tracks reduced transparency preference
 */
interface UsePreferredReducedTransparencyReturn {
  reducedTransparency: Ref<'no-preference' | 'reduce'>;
}

Type Definitions

/** 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

docs

browser-apis.md

browser-events.md

device-sensors.md

element-tracking.md

index.md

mouse-pointer.md

scroll-resize.md

theme-preferences.md

utilities-advanced.md

window-document.md

tile.json