React

Come tematizzare shadcn/ui con le variabili CSS

Cambia i token in :root e .dark, esponili a Tailwind con @theme inline e misura il contrasto: le classi dei componenti non si toccano.

In sintesi

  • Un tema shadcn è un insieme di token semantici in `:root` e `.dark`, non un insieme di classi nuove.
  • Ogni token deve essere mappato in `@theme inline`, altrimenti Tailwind non genera la utility corrispondente.
  • Il contrasto si misura in entrambi i temi: 4.5:1 per il testo normale, 3:1 per il testo grande.

Risposta breve: cambia i valori dei token in :root e .dark nel CSS globale, esponili a Tailwind con @theme inline e lascia intatte le classi dei componenti. Il tema è la mappa dei ruoli, non una seconda copia dei componenti.

Dove vivono i token

Il CLI legge components.json: il campo tailwind.css indica il file globale, tailwind.cssVariables dice se i componenti usano token o utility inline. Con true, che è il valore predefinito, ogni classe parla per ruoli.

{
  "tailwind": {
    "config": "",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  }
}

Il motivo per cui funziona è semplice: bg-primary non contiene un colore, contiene var(--primary). Cambi la variabile e cambiano tutti i componenti che la usano, senza toccare un file in components/ui.

I token che contano di più

TokenCosa controlla
background / foregroundSfondo e testo predefiniti della pagina.
card / card-foregroundSuperfici elevate e contenuto che ci sta sopra.
primary / primary-foregroundAzioni principali, stati selezionati, badge attivi.
muted / muted-foregroundTesto secondario, placeholder, stati vuoti.
border / input / ringBordi, campi e anello di focus.
radiusRaggio base da cui deriva l'intera scala.

Il tema minimo, in CSS

@import "tailwindcss";
@import "shadcn/tailwind.css";

@custom-variant dark (&:is(.dark *));

:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.55 0.18 255);
  --primary-foreground: oklch(0.985 0 0);
  --radius: 0.625rem;
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --primary: oklch(0.72 0.14 255);
  --primary-foreground: oklch(0.205 0 0);
}

Le variabili da sole non bastano: Tailwind deve sapere che --primary è un colore usabile nelle utility. La mappatura sta in @theme inline, e senza di lei bg-primary non viene generata.

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --radius-lg: var(--radius);
}

Fonte: la pagina Theming di shadcn/ui (licenza MIT) descrive gli stessi token e la stessa mappatura; la soglia di contrasto arriva da WCAG 2.2 AA, non da una preferenza estetica.

Aggiungere un token nuovo

Un colore di stato, per esempio un avviso, richiede tre passaggi: valore chiaro in :root, valore scuro in .dark, mappatura in @theme inline. Salta il terzo e la variabile esiste nel DOM ma nessuna classe la legge.

:root {
  --warning: oklch(0.84 0.16 84);
  --warning-foreground: oklch(0.28 0.07 46);
}

.dark {
  --warning: oklch(0.41 0.11 46);
  --warning-foreground: oklch(0.99 0.02 95);
}

@theme inline {
  --color-warning: var(--warning);
  --color-warning-foreground: var(--warning-foreground);
}

Contrasto: l'unico controllo che non puoi rimandare

Misura ogni coppia testo-sfondo nei due temi: 4.5:1 per il testo normale, 3:1 per il testo grande, come chiede WCAG 2.2 AA. Il token che fallisce per primo è sempre muted-foreground, perché nasce già attenuato. Il metodo è descritto in palette UI e contrasto misurato.

Limite dichiarato

I token governano solo ciò che i componenti leggono. Un blocco copiato con classi come bg-zinc-950 resta fuori dal tema, e cssVariables è una scelta fatta all'installazione: per cambiarla la documentazione ufficiale chiede di cancellare e reinstallare i componenti.

Dove continuare

Nel catalogo EneUI trovi la libreria shadcn, il record del componente chart e un blocco chart con anteprima. Per il tema scuro leggi dark mode senza un secondo design system e dark mode senza flash; per i calcoli sui token, Tailwind calc con variabili CSS. Il confronto tra temi pronti è in temi shadcn gratis o premium.

Domande frequenti

Devo modificare i file in components/ui per cambiare tema?

No. Se i componenti usano i token semantici, cambi i valori in :root e .dark e le classi restano quelle: bg-primary legge sempre var(--primary), qualunque sia il colore.

Perché bg-primary non cambia dopo la modifica?

Quasi sempre perché manca la mappatura in @theme inline, oppure perché il CSS modificato non è quello indicato in components.json. Controlla entrambi prima di toccare i componenti.

Come aggiungo un colore di stato, per esempio un warning?

Definisci --warning e --warning-foreground in :root e .dark, poi mappa --color-warning e --color-warning-foreground in @theme inline. Senza la mappatura la utility bg-warning non esiste.

Il tema scuro richiede JavaScript?

Il CSS no: basta la classe .dark su un antenato. Serve JavaScript solo per cambiare la classe, e uno script inline nel head evita il flash del tema sbagliato al primo caricamento.

Posso usare valori hex invece di oklch?

Sì. Le variabili CSS accettano qualsiasi sintassi di colore valida; oklch è il valore predefinito dei temi shadcn recenti, non un requisito. Il contrasto va misurato comunque.