0
# Theme and Preferences
1
2
Components for managing color schemes, dark mode, and user preferences.
3
4
## Capabilities
5
6
### UseColorMode Component
7
8
Manages color scheme preferences with support for system, light, and dark modes.
9
10
```typescript { .api }
11
/**
12
* Component that manages color scheme preferences
13
* @example
14
* <UseColorMode v-slot="{ mode, system, store }">
15
* <div>Current mode: {{ mode }} (System: {{ system }})</div>
16
* </UseColorMode>
17
*/
18
interface UseColorModeProps {
19
/** Color mode selector attribute @default 'class' */
20
selector?: string;
21
/** CSS attribute for mode application @default 'class' */
22
attribute?: string;
23
/** Default color mode @default 'auto' */
24
defaultValue?: BasicColorMode | string;
25
/** Storage key for persistence @default 'vueuse-color-scheme' */
26
storageKey?: string;
27
/** Storage interface @default localStorage */
28
storage?: StorageLike;
29
/** Emit 'auto' mode as preference @default true */
30
emitAuto?: boolean;
31
/** Transition CSS selector @default undefined */
32
transition?: ViewTransition | string;
33
/** Disable transitions @default false */
34
disableTransition?: boolean;
35
/** Storage serializer */
36
serializer?: Serializer<string>;
37
/** Available modes */
38
modes?: (BasicColorMode | string)[];
39
/** Custom mode change handler */
40
onChanged?: (mode: ColorMode, defaultHandler: (mode: ColorMode) => void) => void;
41
}
42
43
/** Slot data exposed by UseColorMode component */
44
interface UseColorModeReturn<T extends string = BasicColorMode> {
45
/** Current color mode */
46
mode: WritableComputedRef<T | BasicColorMode>;
47
/** System color mode preference */
48
system: ComputedRef<BasicColorMode>;
49
/** Store/change color mode */
50
store: WritableComputedRef<T | BasicColorMode>;
51
}
52
53
type BasicColorMode = 'light' | 'dark' | 'auto';
54
type ColorMode = BasicColorMode | string;
55
56
interface StorageLike {
57
getItem(key: string): string | null;
58
setItem(key: string, value: string): void;
59
removeItem(key: string): void;
60
}
61
62
interface ViewTransition {
63
ready: Promise<void>;
64
}
65
66
interface Serializer<T> {
67
read(value: any): T;
68
write(value: T): string;
69
}
70
```
71
72
**Usage Examples:**
73
74
```vue
75
<template>
76
<!-- Basic color mode -->
77
<UseColorMode v-slot="{ mode, system }">
78
<div class="color-mode-demo">
79
<h3>Color Mode Manager</h3>
80
<p>Current mode: {{ mode }}</p>
81
<p>System preference: {{ system }}</p>
82
<div class="mode-controls">
83
<button @click="setMode('light')" :class="{ active: mode === 'light' }">
84
☀️ Light
85
</button>
86
<button @click="setMode('dark')" :class="{ active: mode === 'dark' }">
87
🌙 Dark
88
</button>
89
<button @click="setMode('auto')" :class="{ active: mode === 'auto' }">
90
🔄 Auto
91
</button>
92
</div>
93
</div>
94
</UseColorMode>
95
96
<!-- Custom modes -->
97
<UseColorMode
98
:modes="['light', 'dark', 'sepia', 'contrast']"
99
default-value="light"
100
v-slot="{ mode, store }"
101
>
102
<div class="custom-modes">
103
<h3>Custom Color Modes</h3>
104
<p>Active mode: {{ mode }}</p>
105
<div class="custom-controls">
106
<button
107
v-for="customMode in ['light', 'dark', 'sepia', 'contrast']"
108
:key="customMode"
109
@click="store = customMode"
110
:class="{ active: mode === customMode }"
111
>
112
{{ customMode }}
113
</button>
114
</div>
115
</div>
116
</UseColorMode>
117
118
<!-- With transitions -->
119
<UseColorMode
120
transition="view-transition"
121
:disable-transition="false"
122
v-slot="{ mode, store }"
123
>
124
<div class="transition-demo">
125
<h3>Mode with Transitions</h3>
126
<div class="theme-preview" :data-theme="mode">
127
<p>This content transitions smoothly</p>
128
<button @click="store = mode === 'light' ? 'dark' : 'light'">
129
Toggle Theme
130
</button>
131
</div>
132
</div>
133
</UseColorMode>
134
</template>
135
136
<script setup>
137
import { UseColorMode } from '@vueuse/components';
138
139
// This function would need to be implemented with access to the slot data
140
function setMode(newMode) {
141
// Implementation depends on how you access the slot data
142
}
143
</script>
144
145
<style>
146
.mode-controls, .custom-controls {
147
display: flex;
148
gap: 10px;
149
margin-top: 15px;
150
}
151
152
.mode-controls button, .custom-controls button {
153
padding: 8px 16px;
154
border: 2px solid #ddd;
155
border-radius: 6px;
156
background: white;
157
cursor: pointer;
158
transition: all 0.2s;
159
}
160
161
.mode-controls button.active, .custom-controls button.active {
162
background: #2196f3;
163
color: white;
164
border-color: #1976d2;
165
}
166
167
.theme-preview {
168
padding: 20px;
169
border: 2px solid #ddd;
170
border-radius: 8px;
171
transition: all 0.3s ease;
172
}
173
174
.theme-preview[data-theme="dark"] {
175
background: #333;
176
color: white;
177
border-color: #555;
178
}
179
180
.theme-preview[data-theme="sepia"] {
181
background: #f4f3e8;
182
color: #5c4b37;
183
}
184
185
.theme-preview[data-theme="contrast"] {
186
background: black;
187
color: white;
188
border-color: white;
189
}
190
</style>
191
```
192
193
### UseDark Component
194
195
Manages dark mode state with automatic system preference detection.
196
197
```typescript { .api }
198
/**
199
* Component that manages dark mode state
200
* @example
201
* <UseDark v-slot="{ isDark, toggle }">
202
* <div>Dark mode: {{ isDark ? 'ON' : 'OFF' }}</div>
203
* <button @click="toggle">Toggle</button>
204
* </UseDark>
205
*/
206
interface UseDarkProps {
207
/** CSS selector for dark mode application @default 'html' */
208
selector?: string;
209
/** CSS attribute for dark mode @default 'class' */
210
attribute?: string;
211
/** Default dark mode value @default 'auto' */
212
defaultValue?: boolean | 'auto';
213
/** Storage key for persistence @default 'vueuse-dark' */
214
storageKey?: string;
215
/** Storage interface @default localStorage */
216
storage?: StorageLike;
217
/** Value to set when dark @default 'dark' */
218
valueDark?: string;
219
/** Value to set when light @default '' */
220
valueLight?: string;
221
/** Custom handler for mode changes */
222
onChanged?: (dark: boolean, defaultHandler: (dark: boolean) => void, mode: string) => void;
223
}
224
225
/** Slot data exposed by UseDark component */
226
interface UseDarkReturn {
227
/** Whether dark mode is active */
228
isDark: WritableComputedRef<boolean>;
229
/** Toggle dark mode */
230
toggle: (value?: boolean) => boolean;
231
}
232
```
233
234
**Usage Examples:**
235
236
```vue
237
<template>
238
<!-- Basic dark mode toggle -->
239
<UseDark v-slot="{ isDark, toggle }">
240
<div class="dark-toggle">
241
<div class="theme-indicator" :class="{ dark: isDark }">
242
{{ isDark ? '🌙 Dark Mode' : '☀️ Light Mode' }}
243
</div>
244
<button @click="toggle" class="toggle-btn">
245
Switch to {{ isDark ? 'Light' : 'Dark' }} Mode
246
</button>
247
</div>
248
</UseDark>
249
250
<!-- Custom dark mode values -->
251
<UseDark
252
value-dark="dark-theme"
253
value-light="light-theme"
254
attribute="data-theme"
255
v-slot="{ isDark, toggle }"
256
>
257
<div class="custom-dark">
258
<p>Using custom CSS attribute: data-theme="{{ isDark ? 'dark-theme' : 'light-theme' }}"</p>
259
<button @click="toggle">Toggle Theme</button>
260
</div>
261
</UseDark>
262
263
<!-- Dark mode with storage key -->
264
<UseDark
265
storage-key="my-app-theme"
266
:default-value="false"
267
v-slot="{ isDark, toggle }"
268
>
269
<div class="stored-dark">
270
<h3>Persistent Dark Mode</h3>
271
<p>State persisted as 'my-app-theme': {{ isDark }}</p>
272
<label class="switch">
273
<input type="checkbox" :checked="isDark" @change="toggle" />
274
<span class="slider"></span>
275
</label>
276
</div>
277
</UseDark>
278
</template>
279
280
<script setup>
281
import { UseDark } from '@vueuse/components';
282
</script>
283
284
<style>
285
.dark-toggle {
286
padding: 20px;
287
border: 2px solid #ddd;
288
border-radius: 8px;
289
text-align: center;
290
}
291
292
.theme-indicator {
293
font-size: 1.5em;
294
margin-bottom: 15px;
295
padding: 10px;
296
border-radius: 6px;
297
background: #f9f9f9;
298
transition: all 0.3s;
299
}
300
301
.theme-indicator.dark {
302
background: #333;
303
color: white;
304
}
305
306
.toggle-btn {
307
padding: 10px 20px;
308
border: none;
309
border-radius: 6px;
310
background: #2196f3;
311
color: white;
312
cursor: pointer;
313
font-size: 16px;
314
}
315
316
/* Toggle switch styles */
317
.switch {
318
position: relative;
319
display: inline-block;
320
width: 60px;
321
height: 34px;
322
}
323
324
.switch input {
325
opacity: 0;
326
width: 0;
327
height: 0;
328
}
329
330
.slider {
331
position: absolute;
332
cursor: pointer;
333
top: 0;
334
left: 0;
335
right: 0;
336
bottom: 0;
337
background-color: #ccc;
338
transition: 0.4s;
339
border-radius: 34px;
340
}
341
342
.slider:before {
343
position: absolute;
344
content: "";
345
height: 26px;
346
width: 26px;
347
left: 4px;
348
bottom: 4px;
349
background-color: white;
350
transition: 0.4s;
351
border-radius: 50%;
352
}
353
354
input:checked + .slider {
355
background-color: #2196f3;
356
}
357
358
input:checked + .slider:before {
359
transform: translateX(26px);
360
}
361
</style>
362
```
363
364
### UsePreferredColorScheme Component
365
366
Tracks the system's preferred color scheme.
367
368
```typescript { .api }
369
/**
370
* Component that tracks system preferred color scheme
371
* @example
372
* <UsePreferredColorScheme v-slot="{ colorScheme }">
373
* <div>System prefers: {{ colorScheme }}</div>
374
* </UsePreferredColorScheme>
375
*/
376
interface UsePreferredColorSchemeProps {
377
/** Window object @default defaultWindow */
378
window?: Window;
379
}
380
381
/** Slot data exposed by UsePreferredColorScheme component */
382
interface UsePreferredColorSchemeReturn {
383
/** System color scheme preference */
384
colorScheme: Ref<'light' | 'dark' | 'no-preference'>;
385
}
386
```
387
388
**Usage Examples:**
389
390
```vue
391
<template>
392
<!-- System color scheme detection -->
393
<UsePreferredColorScheme v-slot="{ colorScheme }">
394
<div class="system-scheme">
395
<h3>System Color Scheme</h3>
396
<div class="scheme-display" :class="colorScheme">
397
<span class="scheme-icon">
398
{{ colorScheme === 'dark' ? '🌙' : colorScheme === 'light' ? '☀️' : '❓' }}
399
</span>
400
<span class="scheme-text">{{ colorScheme }}</span>
401
</div>
402
<p class="hint">Change your system theme to see this update</p>
403
</div>
404
</UsePreferredColorScheme>
405
406
<!-- Auto-theme based on system -->
407
<UsePreferredColorScheme v-slot="{ colorScheme }">
408
<div class="auto-theme" :data-theme="colorScheme">
409
<h3>Auto-themed Content</h3>
410
<p>This content automatically adapts to your system theme preference.</p>
411
<p>Current scheme: {{ colorScheme }}</p>
412
</div>
413
</UsePreferredColorScheme>
414
</template>
415
416
<script setup>
417
import { UsePreferredColorScheme } from '@vueuse/components';
418
</script>
419
420
<style>
421
.system-scheme {
422
border: 2px solid #ddd;
423
border-radius: 8px;
424
padding: 20px;
425
text-align: center;
426
}
427
428
.scheme-display {
429
display: flex;
430
align-items: center;
431
justify-content: center;
432
gap: 10px;
433
font-size: 1.5em;
434
padding: 15px;
435
border-radius: 6px;
436
margin: 15px 0;
437
background: #f5f5f5;
438
text-transform: capitalize;
439
}
440
441
.scheme-display.dark {
442
background: #333;
443
color: white;
444
}
445
446
.scheme-display.light {
447
background: #fff;
448
color: #333;
449
border: 1px solid #ddd;
450
}
451
452
.scheme-display.no-preference {
453
background: #e0e0e0;
454
color: #666;
455
}
456
457
.auto-theme {
458
padding: 20px;
459
border-radius: 8px;
460
transition: all 0.3s;
461
border: 2px solid #ddd;
462
}
463
464
.auto-theme[data-theme="dark"] {
465
background: #1a1a1a;
466
color: white;
467
border-color: #333;
468
}
469
470
.auto-theme[data-theme="light"] {
471
background: white;
472
color: #333;
473
border-color: #ddd;
474
}
475
476
.hint {
477
font-size: 0.9em;
478
color: #666;
479
font-style: italic;
480
margin-top: 10px;
481
}
482
</style>
483
```
484
485
### UsePreferredDark Component
486
487
Tracks whether the user prefers dark mode.
488
489
```typescript { .api }
490
/**
491
* Component that tracks dark mode preference
492
* @example
493
* <UsePreferredDark v-slot="{ prefersDark }">
494
* <div>Prefers dark: {{ prefersDark ? 'Yes' : 'No' }}</div>
495
* </UsePreferredDark>
496
*/
497
interface UsePreferredDarkProps {
498
/** Window object @default defaultWindow */
499
window?: Window;
500
}
501
502
/** Slot data exposed by UsePreferredDark component */
503
interface UsePreferredDarkReturn {
504
/** Whether user prefers dark mode */
505
prefersDark: Ref<boolean>;
506
}
507
```
508
509
**Usage Examples:**
510
511
```vue
512
<template>
513
<!-- Dark preference detection -->
514
<UsePreferredDark v-slot="{ prefersDark }">
515
<div class="dark-preference" :class="{ 'prefers-dark': prefersDark }">
516
<h3>Dark Mode Preference</h3>
517
<div class="preference-indicator">
518
<span class="icon">{{ prefersDark ? '🌙' : '☀️' }}</span>
519
<span class="text">
520
Your system {{ prefersDark ? 'prefers dark mode' : 'prefers light mode' }}
521
</span>
522
</div>
523
</div>
524
</UsePreferredDark>
525
526
<!-- Conditional rendering based on preference -->
527
<UsePreferredDark v-slot="{ prefersDark }">
528
<div class="conditional-content">
529
<h3>Smart Default Theme</h3>
530
<div v-if="prefersDark" class="dark-content">
531
🌙 Dark mode content and styling
532
</div>
533
<div v-else class="light-content">
534
☀️ Light mode content and styling
535
</div>
536
</div>
537
</UsePreferredDark>
538
</template>
539
540
<script setup>
541
import { UsePreferredDark } from '@vueuse/components';
542
</script>
543
544
<style>
545
.dark-preference {
546
padding: 20px;
547
border-radius: 8px;
548
border: 2px solid #ddd;
549
transition: all 0.3s;
550
}
551
552
.dark-preference.prefers-dark {
553
background: #1e1e1e;
554
color: white;
555
border-color: #333;
556
}
557
558
.preference-indicator {
559
display: flex;
560
align-items: center;
561
gap: 10px;
562
font-size: 1.2em;
563
}
564
565
.dark-content, .light-content {
566
padding: 15px;
567
border-radius: 6px;
568
text-align: center;
569
font-size: 1.1em;
570
}
571
572
.dark-content {
573
background: #2c2c2c;
574
color: white;
575
}
576
577
.light-content {
578
background: #f9f9f9;
579
color: #333;
580
border: 1px solid #ddd;
581
}
582
</style>
583
```
584
585
### UsePreferredLanguages Component
586
587
Tracks user's preferred languages from browser settings.
588
589
```typescript { .api }
590
/**
591
* Component that tracks user's preferred languages
592
* @example
593
* <UsePreferredLanguages v-slot="{ languages }">
594
* <div>Languages: {{ languages.join(', ') }}</div>
595
* </UsePreferredLanguages>
596
*/
597
interface UsePreferredLanguagesProps {
598
/** Window object @default defaultWindow */
599
window?: Window;
600
}
601
602
/** Slot data exposed by UsePreferredLanguages component */
603
interface UsePreferredLanguagesReturn {
604
/** Array of preferred language codes */
605
languages: Ref<readonly string[]>;
606
}
607
```
608
609
**Usage Examples:**
610
611
```vue
612
<template>
613
<!-- Language preference display -->
614
<UsePreferredLanguages v-slot="{ languages }">
615
<div class="languages-info">
616
<h3>Preferred Languages</h3>
617
<div class="languages-list">
618
<div
619
v-for="(lang, index) in languages"
620
:key="lang"
621
class="language-item"
622
:class="{ primary: index === 0 }"
623
>
624
<span class="flag">{{ getFlagEmoji(lang) }}</span>
625
<span class="code">{{ lang }}</span>
626
<span class="name">{{ getLanguageName(lang) }}</span>
627
<span v-if="index === 0" class="primary-label">Primary</span>
628
</div>
629
</div>
630
</div>
631
</UsePreferredLanguages>
632
633
<!-- Internationalization helper -->
634
<UsePreferredLanguages v-slot="{ languages }">
635
<div class="i18n-helper">
636
<h3>Internationalization</h3>
637
<p>Primary language: {{ languages[0] }}</p>
638
<p>Supported by app: {{ isLanguageSupported(languages[0]) ? 'Yes' : 'No' }}</p>
639
<div v-if="!isLanguageSupported(languages[0])" class="fallback">
640
<p>Falling back to: {{ getFallbackLanguage(languages) }}</p>
641
</div>
642
</div>
643
</UsePreferredLanguages>
644
</template>
645
646
<script setup>
647
import { UsePreferredLanguages } from '@vueuse/components';
648
649
const supportedLanguages = ['en', 'es', 'fr', 'de', 'it', 'pt', 'ja', 'ko', 'zh'];
650
651
function getFlagEmoji(lang) {
652
const flags = {
653
'en': '🇺🇸', 'es': '🇪🇸', 'fr': '🇫🇷', 'de': '🇩🇪', 'it': '🇮🇹',
654
'pt': '🇵🇹', 'ja': '🇯🇵', 'ko': '🇰🇷', 'zh': '🇨🇳'
655
};
656
return flags[lang.split('-')[0]] || '🌐';
657
}
658
659
function getLanguageName(lang) {
660
const names = {
661
'en': 'English', 'es': 'Español', 'fr': 'Français', 'de': 'Deutsch',
662
'it': 'Italiano', 'pt': 'Português', 'ja': '日本語', 'ko': '한국어', 'zh': '中文'
663
};
664
return names[lang.split('-')[0]] || lang;
665
}
666
667
function isLanguageSupported(lang) {
668
return supportedLanguages.includes(lang.split('-')[0]);
669
}
670
671
function getFallbackLanguage(languages) {
672
const supported = languages.find(lang => isLanguageSupported(lang));
673
return supported || 'en';
674
}
675
</script>
676
677
<style>
678
.languages-list {
679
display: flex;
680
flex-direction: column;
681
gap: 8px;
682
margin-top: 15px;
683
}
684
685
.language-item {
686
display: flex;
687
align-items: center;
688
gap: 10px;
689
padding: 8px 12px;
690
border: 1px solid #ddd;
691
border-radius: 6px;
692
background: #f9f9f9;
693
}
694
695
.language-item.primary {
696
background: #e3f2fd;
697
border-color: #2196f3;
698
}
699
700
.flag {
701
font-size: 1.2em;
702
}
703
704
.code {
705
font-family: monospace;
706
font-weight: bold;
707
min-width: 50px;
708
}
709
710
.name {
711
flex: 1;
712
}
713
714
.primary-label {
715
background: #2196f3;
716
color: white;
717
padding: 2px 8px;
718
border-radius: 12px;
719
font-size: 0.8em;
720
}
721
722
.fallback {
723
margin-top: 10px;
724
padding: 10px;
725
background: #fff3cd;
726
border: 1px solid #ffeaa7;
727
border-radius: 6px;
728
}
729
</style>
730
```
731
732
## Additional Preference Components
733
734
### UsePreferredContrast Component
735
736
```typescript { .api }
737
/**
738
* Component that tracks preferred contrast setting
739
*/
740
interface UsePreferredContrastReturn {
741
contrast: Ref<'no-preference' | 'high' | 'low'>;
742
}
743
```
744
745
### UsePreferredReducedMotion Component
746
747
```typescript { .api }
748
/**
749
* Component that tracks reduced motion preference
750
*/
751
interface UsePreferredReducedMotionReturn {
752
reducedMotion: Ref<'no-preference' | 'reduce'>;
753
}
754
```
755
756
### UsePreferredReducedTransparency Component
757
758
```typescript { .api }
759
/**
760
* Component that tracks reduced transparency preference
761
*/
762
interface UsePreferredReducedTransparencyReturn {
763
reducedTransparency: Ref<'no-preference' | 'reduce'>;
764
}
765
```
766
767
## Type Definitions
768
769
```typescript { .api }
770
/** Common types used across theme and preference components */
771
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);
772
773
interface RenderableComponent {
774
/** The element that the component should be rendered as @default 'div' */
775
as?: object | string;
776
}
777
778
/** Color mode types */
779
type BasicColorMode = 'light' | 'dark' | 'auto';
780
type ColorMode = BasicColorMode | string;
781
782
/** Preference types */
783
type ContrastPreference = 'no-preference' | 'high' | 'low';
784
type ReducedMotionPreference = 'no-preference' | 'reduce';
785
type ReducedTransparencyPreference = 'no-preference' | 'reduce';
786
787
/** Storage interface */
788
interface StorageLike {
789
getItem(key: string): string | null;
790
setItem(key: string, value: string): void;
791
removeItem(key: string): void;
792
}
793
```