⌨️ Sviluppo web

Variabili CSS (custom properties): guida pratica

Come funzionano le variabili CSS: dichiarazione, ereditarietà, valori di riserva, temi chiaro e scuro e modifica da JavaScript, con esempi pronti da usare.

Per anni le variabili nel CSS sono state una prerogativa dei preprocessori come Sass o Less. Oggi il CSS ha le sue custom properties, comunemente chiamate variabili CSS, e sono molto più potenti di quelle dei preprocessori perché vivono nel browser: si ereditano, cambiano in base al contesto e si possono modificare a runtime.

Sintassi di base

Una custom property è una proprietà il cui nome inizia con due trattini. Si legge con la funzione var().

:root {
  --colore-primario: #1a5fb4;
  --spazio: 1rem;
  --raggio: 6px;
}

.button {
  background: var(--colore-primario);
  padding: var(--spazio) calc(var(--spazio) * 2);
  border-radius: var(--raggio);
}

Il selettore :root corrisponde all'elemento html: le variabili dichiarate lì sono disponibili in tutta la pagina. I nomi sono sensibili alle maiuscole, quindi --Colore e --colore sono due variabili diverse.

La differenza con le variabili di Sass

Una variabile Sass viene sostituita al momento della compilazione: nel CSS finale resta solo il valore. Una custom property, invece, esiste davvero nel documento e segue la cascata e l'ereditarietà come qualunque altra proprietà. Questo significa che puoi ridefinirla in un punto preciso dell'albero e tutti i discendenti useranno il nuovo valore.

.card {
  --colore-primario: #26a269;
}

Ogni pulsante dentro .card diventa verde senza toccare la regola .button. È questo il meccanismo che rende le variabili CSS ideali per varianti di componenti e temi.

Valori di riserva

La funzione var() accetta un secondo argomento usato quando la variabile non è definita:

.badge {
  color: var(--badge-colore, #333);
}

Il valore di riserva può contenere a sua volta un'altra variabile: var(--a, var(--b, black)). Attenzione però: se la variabile è definita ma con un valore non valido per quella proprietà, il browser non usa il valore di riserva, bensì il valore iniziale o ereditato della proprietà. Per esempio, --larghezza: rosso usato in width produce un risultato inatteso senza alcun errore visibile.

Temi chiaro e scuro

Le variabili rendono i temi quasi banali. Definisci i colori come token semantici e ridefiniscili in base alla preferenza del sistema:

:root {
  --sfondo: #ffffff;
  --testo: #1c1c1c;
  --bordo: #d0d0d0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --sfondo: #161616;
    --testo: #ececec;
    --bordo: #3a3a3a;
  }
}

body {
  background: var(--sfondo);
  color: var(--testo);
}

Se vuoi offrire anche un selettore manuale, puoi aggiungere un attributo come data-theme="dark" sull'elemento html e ridefinire le stesse variabili per quel selettore. Il resto del foglio di stile non cambia.

Nomi semantici, non descrittivi

Un consiglio che vale la pena seguire: chiama le variabili in base al ruolo (--testo, --sfondo-evidenza, --colore-errore) e non in base al valore (--blu, --grigio-chiaro). Quando il tema scuro trasforma il "grigio chiaro" in un grigio scuro, un nome descrittivo diventa fuorviante.

Modificare le variabili da JavaScript

Poiché vivono nel DOM, le custom properties si leggono e si scrivono con le normali API degli stili:

const root = document.documentElement;

// lettura
const colore = getComputedStyle(root).getPropertyValue('--colore-primario');

// scrittura
root.style.setProperty('--colore-primario', '#c01c28');

Questo apre usi interessanti: passare al CSS la posizione del mouse per un effetto di luce, collegare un cursore alla dimensione del testo, oppure aggiornare una barra di avanzamento impostando solo --progresso e lasciando al CSS il calcolo della larghezza.

.progress-bar {
  width: calc(var(--progresso, 0) * 1%);
}

Variabili e calc()

Le custom properties memorizzano testo, non numeri con unità. Se dichiari --colonne: 3, per usarla come misura devi moltiplicarla: calc(var(--colonne) * 100px). Al contrario, non puoi "incollare" un'unità a una variabile scrivendo var(--n)px: non funziona. Mantieni nelle variabili numeri puri quando devi fare calcoli, e unità complete quando ti serve un valore pronto.

@property: variabili tipizzate

La regola @property permette di registrare una variabile con un tipo, un valore iniziale e la scelta se ereditarla o no:

@property --angolo {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

Il vantaggio principale è che il browser sa che --angolo è un angolo, quindi può animarlo. Una variabile non registrata, invece, cambia di colpo durante una transizione, perché il browser non sa come interpolare del testo generico.

Errori frequenti

  • Dichiarare tutto in :root anche quando una variabile serve solo a un componente: meglio dichiararla sul componente stesso, così resta locale.
  • Creare centinaia di variabili per valori usati una sola volta: le variabili servono a ciò che si ripete o cambia.
  • Dimenticare il valore di riserva nei componenti riutilizzabili, che potrebbero finire in pagine dove il token non esiste.
  • Usare le variabili nei selettori o nei nomi delle media query: lì non sono ammesse, perché funzionano solo nei valori delle proprietà.

In sintesi

  • Le custom properties iniziano con -- e si leggono con var().
  • Seguono cascata ed ereditarietà, quindi si possono ridefinire per singoli componenti.
  • Sono il modo più semplice per gestire temi chiaro e scuro con token semantici.
  • JavaScript può leggerle e modificarle a runtime con setProperty.
  • @property le rende tipizzate e animabili.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica, unisce il rigore dell'analisi tecnica all'attenzione per le persone che usano la tecnologia.

Leggi anche