Dark Mode
@pzeta/vue-components supporta il dark mode tramite la classe CSS .dark applicata a un elemento antenato. Non richiede variabili di sistema (prefers-color-scheme): il controllo è esplicito, lasciando all'applicazione la scelta del momento di attivazione.
Configurazione Tailwind 4
Il dark mode è configurato in src/assets/main.css con una custom variant:
@custom-variant dark (&:where(.dark, .dark *));
Questo configura la variante dark: di Tailwind per attivarsi quando l'elemento o uno dei suoi antenati ha la classe .dark. È equivalente alla modalità selector di Tailwind v3 con selettore .dark.
Attivazione
Aggiungere la classe .dark all'elemento <html> (o qualsiasi contenitore che avvolge l'interfaccia):
<!-- Light mode (default) -->
<html>
<body>
<div id="app">...</div>
</body>
</html>
<!-- Dark mode -->
<html class="dark">
<body>
<div id="app">...</div>
</body>
</html>
Con Vue 3 (toggle dinamico):
// composable/useTheme.ts
import { ref, watchEffect } from 'vue'
const isDark = ref(false)
export function useTheme() {
watchEffect(() => {
document.documentElement.classList.toggle('dark', isDark.value)
})
return { isDark }
}
CSS Variables overrides
Il selettore .dark nella libreria sovrascrive i token semantici di superficie e testo. Le severity colors (primary, success, danger, ecc.) non hanno override in dark mode: la loro scala numerica è usata direttamente con shade appropriati.
.dark {
/* Superfici */
--color-surface: #1f2937; /* gray-800 */
--color-surface-secondary: #111827; /* gray-900 */
--color-surface-hover: #374151; /* gray-700 */
--color-background: #0f172a; /* slate-900 */
--color-overlay: 3 7 18; /* gray-950 RGB */
/* Testo */
--color-text: #f9fafb; /* gray-50 */
--color-text-secondary: #9ca3af; /* gray-400 */
--color-text-muted: #6b7280; /* gray-500 */
--color-label: #e5e7eb; /* gray-200 */
/* ScrollPanel */
--scrollpanel-track-color: #374151; /* gray-700 */
--scrollpanel-thumb-color: #6b7280; /* gray-500 */
--scrollpanel-thumb-hover-color: #9ca3af; /* gray-400 */
}
Tutti i componenti che usano queste CSS variables si adattano automaticamente al dark mode senza classi dark: aggiuntive.
Classi dark: nei componenti
Per i colori che non usano le CSS variables (bordi, testo su severity, ecc.), i componenti usano le classi Tailwind dark::
/* InputText - variante outlined in dark */
.hs-inputtext-variant-outlined {
@apply border-gray-300 bg-transparent text-gray-900;
@apply dark:border-gray-600 dark:bg-transparent dark:text-gray-100;
}
/* Button text in dark */
.hs-button-text-primary {
@apply text-primary-600 hover:bg-primary-50;
@apply dark:text-primary-400 dark:hover:bg-primary-900/20;
}
/* Badge subtle in dark */
.hs-badge.severity-primary.variant-subtle {
@apply bg-primary-100 text-primary-800;
@apply dark:bg-primary-900/30 dark:text-primary-300;
}
/* Contrast severity (inversione completa) */
.hs-button-contrast {
@apply bg-contrast-800 text-white hover:bg-contrast-900;
@apply dark:bg-contrast-200 dark:text-contrast-900;
}
Token dark mode per severity
Le severity non hanno override globale, ma i componenti usano shade più chiari in dark mode:
| Severità | Light mode | Dark mode |
|---|---|---|
| Primary text | text-primary-600 | dark:text-primary-400 |
| Primary bg subtle | bg-primary-100 | dark:bg-primary-900/30 |
| Success text | text-success-600 | dark:text-success-400 |
| Success bg subtle | bg-success-100 text-success-700 | dark:bg-success-900/30 dark:text-success-300 |
| Danger text | text-danger-600 | dark:text-danger-400 |
| Contrast bg | bg-contrast-800 text-white | dark:bg-contrast-200 dark:text-contrast-900 |
Personalizzazione dark mode
Per sovrascrivere i token dark nel proprio progetto:
@import '@pzeta/vue-components/style.css';
/* Override dei surface colors in dark mode */
.dark {
--color-surface: #0d1117; /* GitHub dark style */
--color-surface-secondary: #161b22;
--color-background: #010409;
--color-text: #e6edf3;
--color-label: #cdd9e5;
}
Persistenza della preferenza
Esempio di implementazione con localStorage:
// src/composables/useTheme.ts
import { ref, watchEffect } from 'vue'
const STORAGE_KEY = 'theme'
const isDark = ref(localStorage.getItem(STORAGE_KEY) === 'dark')
export function useTheme() {
watchEffect(() => {
document.documentElement.classList.toggle('dark', isDark.value)
localStorage.setItem(STORAGE_KEY, isDark.value ? 'dark' : 'light')
})
function toggleTheme() {
isDark.value = !isDark.value
}
function setSystemTheme() {
isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches
}
return { isDark, toggleTheme, setSystemTheme }
}
<template>
<Button
:icon="isDark ? 'pi pi-sun' : 'pi pi-moon'"
text
@click="toggleTheme"
/>
</template>
<script setup lang="ts">
import { useTheme } from '@/composables/useTheme'
const { isDark, toggleTheme } = useTheme()
</script>