Theming

Dark Mode

Configurazione dark mode con Tailwind 4 e CSS tokens.

@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 modeDark mode
Primary texttext-primary-600dark:text-primary-400
Primary bg subtlebg-primary-100dark:bg-primary-900/30
Success texttext-success-600dark:text-success-400
Success bg subtlebg-success-100 text-success-700dark:bg-success-900/30 dark:text-success-300
Danger texttext-danger-600dark:text-danger-400
Contrast bgbg-contrast-800 text-whitedark: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>