or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

browser-apis.mdbrowser-events.mddevice-sensors.mdelement-tracking.mdindex.mdmouse-pointer.mdscroll-resize.mdtheme-preferences.mdutilities-advanced.mdwindow-document.md

theme-preferences.mddocs/

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

```