Cerca...

Cerca nella documentazione...

Theming

Theming

Vesuvius UI separa due assi indipendenti: modalità chiara/scura e tema colore (palette). Entrambi sono gestiti da ThemeProvider, montato una sola volta alla radice della tua app.

import { ThemeProvider } from "@pompeitech/vesuvius-ui";
 
<ThemeProvider defaultTheme="system" defaultColorTheme="lava">
  <App />
</ThemeProvider>;

API

ThemeProvider

PropTipoDefaultDescrizione
defaultTheme"light" | "dark" | "system""system"Modalità applicata prima che l'utente ne scelga una. "system" segue prefers-color-scheme e continua ad ascoltare i cambi a livello di sistema operativo.
defaultColorThemeColorTheme"lava"Tema colore applicato prima che l'utente ne scelga uno.
colorThemesreadonly ColorTheme[]tutti e 10 i predefinitiL'insieme completo dei temi colore selezionabili — passa un sottoinsieme per limitare cosa ThemePalettePicker/ThemeSwitcher offrono, oppure aggiungi il nome di un tuo tema personalizzato (vedi Un tema personalizzato).
storageKeystring"vesuvius-ui-theme"Chiave localStorage per la scelta della modalità.
colorThemeStorageKeystring"vesuvius-ui-color-theme"Chiave localStorage per la scelta del tema colore.

useTheme()

CampoTipoDescrizione
theme"light" | "dark" | "system"Cosa ha scelto l'utente.
resolvedTheme"light" | "dark"theme con "system" risolto a un valore concreto — usalo per qualsiasi cosa richieda una modalità concreta.
colorThemeColorThemeIl nome della palette attiva.
setTheme(theme: "light" | "dark" | "system") => void
setColorTheme(theme: ColorTheme) => void

Lancia un errore se chiamato fuori da un ThemeProvider — non fallisce silenziosamente con valori undefined.

La modalità viene applicata come classe .dark su <html>; il tema colore come attributo data-theme="<nome>", anch'esso su <html>. Entrambi persistono su localStorage e restano completamente indipendenti — attivare la modalità scura non cambia mai il tema colore, e viceversa, quindi una combinazione come "supabase, dark" o "vercel, light" sopravvive a un reload.

Theme tokens

Ogni colore usato dai componenti è una custom property CSS, mai un colore Tailwind hardcoded — quindi ri-tematizzare il kit è interamente un esercizio sulle variabili CSS, senza toccare codice dei componenti. Questa è esattamente la convenzione usata da shadcn/ui, e Vesuvius la segue deliberatamente: ogni token grezzo (--primary, --background, ...) viene impostato una volta sotto :root/.dark, poi ri-esposto a Tailwind v4 tramite un blocco @theme inline:

@theme inline {
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  /* ...una riga per token, stesso pattern per tutti */
}

Quella indirezione --color-* è ciò che fa esistere bg-primary, text-primary-foreground, border-border, ring-ring e ogni altra utility class tematizzata — i componenti scrivono usando quelle utility e non vedono mai direttamente il valore grezzo di --primary, quindi sovrascrivere un token ri-tematizza istantaneamente ogni componente che lo usa, senza alcuna modifica a livello di componente.

Token di base

TokenUtility classUsato per
--backgroundbg-backgroundSfondo della pagina, dietro ogni superficie.
--foregroundtext-foregroundColore di testo predefinito del corpo.
--cardbg-cardSfondo della superficie card/pannello.
--card-foregroundtext-card-foregroundTesto sopra una superficie card.
--popoverbg-popoverSfondo della superficie dropdown/popover/tooltip.
--popover-foregroundtext-popover-foregroundTesto sopra una superficie popover.
--primarybg-primaryColore dell'azione primaria — Button di default, stati attivi, accento del brand.
--primary-foregroundtext-primary-foregroundColore di testo/icona sopra --primary.
--primary-emphasistext-primary-emphasisTesto/icone primari accessibili su superfici chiare.
--secondarybg-secondarySuperfici secondarie — la variante secondary di Button, riempimenti tenui.
--secondary-foregroundtext-secondary-foregroundTesto sopra --secondary.
--mutedbg-mutedSfondi a bassa enfasi — stati disabilitati, righe tenui.
--muted-foregroundtext-muted-foregroundTesto a bassa enfasi — didascalie, placeholder, testo di aiuto.
--accentbg-accentSfondo hover/highlight — voci di menu, righe selezionate.
--accent-foregroundtext-accent-foregroundTesto sopra --accent.
--destructivebg-destructiveAzioni distruttive e stati di errore.
--destructive-foregroundtext-destructive-foregroundColore di testo/icona sopra --destructive.
--borderborder-borderColore del bordo predefinito, usato ovunque (card, input, divisori).
--inputborder-inputColore del bordo specifico per i controlli di form (Input, Select, Textarea, ...).
--ringring-ringColore del focus ring su ogni elemento focalizzabile.

Token di stato semantico

Oltre alla convenzione di shadcn, Vesuvius aggiunge quattro coppie dedicate di stato semantico — usate da Alert, Badge, Toast e ovunque uno stato debba avere un significato fisso indipendentemente dal --primary del tema colore attivo:

TokenUtility classUsato per
--success / --success-foregroundbg-success / text-success-foregroundConferme, stati completati.
--success-emphasistext-success-emphasisTesto/icone di successo accessibili su superfici chiare.
--warning / --warning-foregroundbg-warning / text-warning-foregroundCautela, stati vicini al limite.
--warning-emphasistext-warning-emphasisTesto/icone di avviso accessibili su superfici chiare.
--info / --info-foregroundbg-info / text-info-foregroundCallout informativi neutri.
--info-emphasistext-info-emphasisTesto/icone informativi accessibili su superfici chiare.
--highlight / --highlight-foregroundbg-highlight / text-highlight-foregroundEnfasi in stile "novità"/annuncio, distinta da --primary.
--highlight-emphasistext-highlight-emphasisTesto/icone di enfasi accessibili su superfici chiare.

Token dei grafici

TokenUtility classUsato per
--chart-1 … --chart-5fill-chart-1 … fill-chart-5, ecc.Una scala di colori categorica a 5 passi per Charts, SimpleRadarChart, Sparkline, ChartCard — usata ciclicamente man mano che si aggiungono serie.

Token della sidebar

Il componente Sidebar ha una propria palette dedicata invece di riusare i token di base, così una sidebar può intenzionalmente leggersi come una regione di "chrome" distinta (più scura, o tinta diversamente) rispetto all'area di contenuto principale:

TokenUtility classUsato per
--sidebarbg-sidebarSfondo della sidebar.
--sidebar-foregroundtext-sidebar-foregroundTesto predefinito della sidebar.
--sidebar-primary / --sidebar-primary-foregroundbg-sidebar-primary / text-sidebar-primary-foregroundVoce attiva/selezionata della sidebar.
--sidebar-accent / --sidebar-accent-foregroundbg-sidebar-accent / text-sidebar-accent-foregroundVoce della sidebar in hover.
--sidebar-borderborder-sidebar-borderBordi all'interno della sidebar (incluso il proprio bordo esterno).
--sidebar-ringring-sidebar-ringFocus ring sulle voci della sidebar.

Radius e tipografia

TokenDescrizione
--radiusIl raggio d'angolo di base. Ogni raggio dei componenti (--radius-sm, --radius-md, --radius-lg, --radius-xl) è derivato da questo unico valore tramite calc(), quindi cambiare solo --radius riscala in modo coerente ogni angolo arrotondato del kit.
--font-headingFont applicato a <h1>–<h6> e a qualsiasi componente che aderisca allo stile di testo "heading". Il testo del corpo usa lo stack font-sans predefinito di Tailwind, non influenzato da questo token.

Sovrascrivi uno qualsiasi di questi token su :root (e .dark per il valore in modalità scura) nel CSS della tua app, dopo il foglio di stile di Vesuvius, per ri-brandizzare senza forkare il package:

:root {
  --radius: 0.5rem;
  --font-heading: "Il Tuo Font Brand", sans-serif;
}

I 10 temi predefiniti

Il catalogo visuale è disponibile su /themes, con una pagina di dettaglio dedicata a ogni palette. Ogni tema è anche il proprio exports subpath sotto themes/ — importa solo quelli che offri, per uno stile CSS più leggero:

/* Tutti i temi insieme */
@import "@pompeitech/vesuvius-ui/styles.css";
 
/* Oppure solo quello che serve */
@import "@pompeitech/vesuvius-ui/base.css";
@import "@pompeitech/vesuvius-ui/themes/lava.css";

lava

Il tema di punta del kit e default di ThemeProvider — ri-tinteggia ogni superficie (non solo l'accento), con una calda palette "pietra vulcanica": inchiostro scuro su superfici crema in modalità chiara, inchiostro crema su ossidiana quasi nera in modalità scura, entrambi ancorati a un primary arancio bruciato.

@import "@pompeitech/vesuvius-ui/themes/lava.css";

stripe

Una palette fintech-editoriale ispirata a Stripe: canvas bianco, inchiostro blu navy profondo e un accento operativo violetto-blurple.

@import "@pompeitech/vesuvius-ui/themes/stripe.css";

vercel

Una palette netta bianca e nera ispirata alle superfici minimali di Vercel.

@import "@pompeitech/vesuvius-ui/themes/vercel.css";

supabase

Una palette emerald-mint ispirata a Supabase: brillante, tecnica e amichevole.

@import "@pompeitech/vesuvius-ui/themes/supabase.css";

linear

Una palette fredda e contenuta con il primary violet-indigo morbido associato a Linear.

@import "@pompeitech/vesuvius-ui/themes/linear.css";

claude

Una palette calda parchment e terracotta ispirata all'atmosfera di Claude.

@import "@pompeitech/vesuvius-ui/themes/claude.css";

amber-minimal

Una palette bianca pulita con un primary ambra-dorato concentrato.

@import "@pompeitech/vesuvius-ui/themes/amber-minimal.css";

claymorphism

Una palette morbida e dimensionale costruita su un canvas clay di tono medio (#e0e0e0), superfici quasi bianche e un primary viola deciso. I token di contrasto e semantici restano compatibili con il contratto shadcn standard in entrambe le modalità.

@import "@pompeitech/vesuvius-ui/themes/claymorphism.css";

alpine

Una palette SaaS premium ispirata ad Alpine: inchiostro alpine-night profondo, struttura cobalt decisa, calore corallo e un canvas blush morbido (#fcf5f7).

@import "@pompeitech/vesuvius-ui/themes/alpine.css";

aubergine

Una palette ispirata a Slack, con sidebar aubergine, superfici viola espressive e azioni blu brillanti.

@import "@pompeitech/vesuvius-ui/themes/aubergine.css";

Cosa sovrascrive davvero un tema

Un file di tema è composto da custom property CSS sotto [data-theme="<nome>"] (e [data-theme="<nome>"].dark per la sua variante scura). Tutti e dieci i temi predefiniti forniscono una palette completa chiara/scura: superfici, testi, azioni, stati semantici, grafici e sidebar.

Ogni componente legge questi valori come utility Tailwind (bg-primary, text-muted-foreground, border-border, …) generate dal blocco @theme inline in base.css — i componenti non hardcodano mai un colore, quindi un cambio di tema non richiede alcuna modifica a livello di componente.

Far scegliere il tema agli utenti

Tre controlli già pronti leggono/scrivono lo stesso contesto di ThemeProvider:

import { ThemeModeToggle, ThemePalettePicker, ThemeSwitcher } from "@pompeitech/vesuvius-ui";
 
// Un singolo pulsante icona che inverte chiaro/scuro.
<ThemeModeToggle />
 
// Una command palette ricercabile di temi colore.
<ThemePalettePicker />
 
// Sia modalità che tema colore in un unico dropdown.
<ThemeSwitcher />

Tutti e tre chiamano internamente useTheme(), quindi funzionano solo dentro un ThemeProvider.

Un tema personalizzato

Un tema è semplicemente custom property CSS sotto [data-theme="<nome>"] (e [data-theme="<nome>"].dark per la sua variante scura) — aggiungi il tuo senza toccare questo package. Sovrascrivi i token di superficie oltre a quelli di accento se vuoi lo stesso trattamento completo chiaro/scuro dei temi predefiniti.

[data-theme="acme"] {
  --primary: oklch(0.55 0.2 145);
  --primary-foreground: oklch(0.985 0 0);
  --ring: oklch(0.55 0.2 145);
 
  --chart-1: oklch(0.55 0.2 145);
  --chart-2: oklch(0.62 0.18 170);
  --chart-3: oklch(0.5 0.19 120);
  --chart-4: oklch(0.68 0.16 90);
  --chart-5: oklch(0.45 0.18 160);
 
  --sidebar-primary: oklch(0.55 0.2 145);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-ring: oklch(0.55 0.2 145);
}
[data-theme="acme"].dark {
  --primary: oklch(0.7 0.17 145);
  --primary-foreground: oklch(0.145 0 0);
  --ring: oklch(0.7 0.17 145);
 
  --chart-1: oklch(0.7 0.17 145);
  --chart-2: oklch(0.75 0.15 170);
  --chart-3: oklch(0.65 0.16 120);
  --chart-4: oklch(0.8 0.14 90);
  --chart-5: oklch(0.6 0.15 160);
 
  --sidebar-primary: oklch(0.7 0.17 145);
  --sidebar-primary-foreground: oklch(0.145 0 0);
  --sidebar-ring: oklch(0.7 0.17 145);
}

Poi passa il suo nome tramite colorThemes così i picker predefiniti lo elencano anche loro:

import { ThemeProvider, COLOR_THEMES } from "@pompeitech/vesuvius-ui";
 
<ThemeProvider colorThemes={[...COLOR_THEMES, "acme"]} defaultColorTheme="acme">
  <App />
</ThemeProvider>;

Il tipo di colorTheme (ColorTheme) è formato dai 10 nomi predefiniti allargati con string & {} proprio perché un nome personalizzato come "acme" passi il type-check senza dover forkare o estendere i tipi del package.

Preferisci oklch() a hsl()/rgb() quando scrivi nuovi valori di token — ogni tema predefinito è scritto in oklch() proprio perché mantiene coerente la luminosità percepita tra le tonalità, ed è ciò che fa sì che una coppia chiaro/scuro dello stesso tema sembri un abbinamento coerente invece di due colori scollegati.

Difendere i componenti renderizzati dentro il CSS di un altro host

Se i componenti di questo kit vengono renderizzati dentro una pagina che ha un proprio CSS globale (un sito di documentazione, un tema CMS, un altro design system nella stessa pagina) — non solo dentro la tua app — quel CSS host può infiltrarsi in qualsiasi cosa il kit renderizzi come elemento nativo grezzo, specialmente <table> (Calendar, Table/DataTable), dato che un numero sorprendente di CSS reset distribuisce una regola globale non scoperta table { ... }.

Il kit si difende da solo, nel @layer base di base.css, invece di lasciarlo ai consumatori: [data-slot="table"] e .group\/calendar table ricevono reset protetti da !important per display, border-collapse e bordi/padding/sfondo delle celle. !important è necessario proprio per come funzionano i cascade layer CSS — le utility di Tailwind vivono in @layer utilities, e qualsiasi CSS host non scoperto (anche un singolo selettore a bassa specificità) supera sempre qualunque cosa si trovi in un layer, indipendentemente dalla specificità; solo !important inverte questo comportamento. Se incontri lo stesso tipo di bug con un componente che questo kit non protegge ancora, la correzione ha la stessa forma: una regola non scoperta (o !important) scoperta sul data-slot di quel componente.