🧩 Dynamic Theme Switching with JSON in Web Development: guida operativa completa (con 3 file)
Il cambio tema dinamico è un problema di ingegneria del front-end: separare le regole (CSS stabile) dai valori (tema variabile), applicare questi valori in modo deterministico e garantire che la pagina resti leggibile anche se JavaScript o storage falliscono. La soluzione più pulita prevede quattro elementi (HTML, CSS, JS, JSON). In questa guida, però, applichiamo un vincolo pratico: tre file soltanto, come spesso richiesto in contesti reali dove si vuole un esempio copiabile e immediatamente funzionante. Per mantenere il requisito “JSON”, il file JSON dei temi viene incorporato come oggetto JSON dentro theme.js: concettualmente è lo stesso dato, ma non richiede un quarto file o un endpoint. Il sistema supporta: preferenza di sistema (light/dark), scelta esplicita dell’utente, persistenza della scelta e degrado corretto quando una componente non è disponibile.
Nota terminologica: in questo articolo “token” indica design token (variabili CSS come --bg, --text), non token di sicurezza (es. CSRF).
🧠 Fatti, modelli e limiti: cosa è certo e cosa no
Fatti accertati (A): le CSS Custom Properties permettono di definire token riusabili e aggiornabili a runtime; la preferenza di sistema può essere rilevata; JavaScript può impostare token su :root e quindi modificare l’aspetto globale senza duplicare regole.
Modello operativo (B): la scelta utente deve prevalere sulla preferenza di sistema; in assenza di scelta utente si segue l’OS; se qualcosa fallisce si deve tornare a un fallback CSS coerente.
Limiti e incertezze (C): la persistenza può essere limitata (privacy, modalità privata); la “perfezione” anti-flash dipende dal timing di caricamento e dalla cache. In questo esempio si riduce il problema con fallback CSS e applicazione immediata al load.
🧱 Architettura in 3 file: catena causale verificabile
La catena deve essere leggibile e controllabile:
- theme.css definisce token e usa token nelle regole (niente valori hardcoded sparsi).
- theme.js contiene il JSON dei temi (embedded) e applica i token su :root.
- index.php include CSS e JS e fornisce (opzionale) un controllo di switch.
Pietra miliare: in un sistema ben progettato, il CSS non conosce i temi. Conosce solo i token. I temi sono dati. Il JavaScript applica dati ai token. Se per cambiare tema devi riscrivere regole CSS, il sistema è duplicato e fragile.
📄 index.php (file 1/3)
Questo file serve solo a caricare CSS e JS e a mostrare un contenuto test. Il pulsante è volutamente minimale: in un progetto reale potresti integrarlo nel tuo layout o sostituirlo con una voce di menu.
<?php declare(strict_types=1); ?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dynamic Theme Switching with JSON</title>
<link rel="stylesheet" href="theme.css">
<script src="theme.js" defer></script>
</head>
<body>
<h1>Dynamic Theme Switching (JSON + CSS Tokens)</h1>
<p>
Se la soluzione funziona, i colori di sfondo/testo e i pulsanti cambiano tema.
La scelta viene memorizzata e riapplicata al refresh.
</p>
<ul>
<li><button type="button" data-theme-set="light">Light</button></li>
<li><button type="button" data-theme-set="dark">Dark</button></li>
<li><button type="button" data-theme-set="system">System</button></li>
</ul>
<p>
Testo di prova per contrasto e leggibilità. Controlla anche input e bordi.
</p>
<input type="text" placeholder="Type something...">
</body>
</html>
🎨 theme.css (file 2/3)
Il CSS è scritto per dipendere esclusivamente da token. Inoltre include un fallback coerente con la preferenza di sistema, così la pagina resta usabile anche se JS non gira o se storage è bloccato.
:root{
color-scheme: light dark;
--bg: #ffffff;
--text: #111111;
--muted: #444444;
--border: #d0d0d0;
--surface: #f5f5f5;
--accent: #1a73e8;
--btn-bg: #111111;
--btn-text: #ffffff;
--input-bg: #ffffff;
--input-text: #111111;
}
/* fallback automatico su preferenza OS */
@media (prefers-color-scheme: dark){
:root{
--bg: #121212;
--text: #f0f0f0;
--muted: #c8c8c8;
--border: #2a2a2a;
--surface: #1b1b1b;
--accent: #8ab4f8;
--btn-bg: #f0f0f0;
--btn-text: #121212;
--input-bg: #1b1b1b;
--input-text: #f0f0f0;
}
}
html, body{
margin: 0;
padding: 24px;
background: var(--bg);
color: var(--text);
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
line-height: 1.6;
}
h1{
margin: 0 0 14px 0;
}
p{
margin: 0 0 14px 0;
color: var(--text);
}
ul{
margin: 0 0 16px 0;
padding-left: 18px;
}
button{
border: 1px solid var(--border);
background: var(--btn-bg);
color: var(--btn-text);
padding: 10px 12px;
cursor: pointer;
}
input{
border: 1px solid var(--border);
background: var(--input-bg);
color: var(--input-text);
padding: 10px 12px;
width: 320px;
}
🧬 theme.js (file 3/3) con JSON incorporato
Qui sta la parte “JSON”: i temi sono descritti come oggetto JSON (equivalente a un file themes.json). Il codice fa tre cose: recupera la scelta salvata, risolve il tema effettivo (utente/system), applica token su :root. Inoltre limita i token applicabili per evitare che il tema diventi un canale arbitrario.
(() => {
"use strict";
const STORAGE_KEY = "ap_theme_choice";
/* JSON dei temi (equivalente a themes.json) */
const THEMES = {
light: {
"--bg": "#ffffff",
"--text": "#111111",
"--muted": "#444444",
"--border": "#d0d0d0",
"--surface": "#f5f5f5",
"--accent": "#1a73e8",
"--btn-bg": "#111111",
"--btn-text": "#ffffff",
"--input-bg": "#ffffff",
"--input-text": "#111111"
},
dark: {
"--bg": "#121212",
"--text": "#f0f0f0",
"--muted": "#c8c8c8",
"--border": "#2a2a2a",
"--surface": "#1b1b1b",
"--accent": "#8ab4f8",
"--btn-bg": "#f0f0f0",
"--btn-text": "#121212",
"--input-bg": "#1b1b1b",
"--input-text": "#f0f0f0"
}
};
const ALLOWED_TOKENS = new Set(Object.keys(THEMES.light));
function systemTheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function safeGet() {
try { return localStorage.getItem(STORAGE_KEY); } catch { return null; }
}
function safeSet(v) {
try { localStorage.setItem(STORAGE_KEY, v); } catch {}
}
function applyTheme(choice) {
const themeName = (!choice || choice === "system") ? systemTheme() : choice;
const tokens = THEMES[themeName];
if (!tokens) return;
const root = document.documentElement;
for (const [k, v] of Object.entries(tokens)) {
if (!ALLOWED_TOKENS.has(k)) continue;
if (typeof v !== "string") continue;
root.style.setProperty(k, v);
}
}
function bindUI() {
document.addEventListener("click", (e) => {
const t = e.target;
if (!(t instanceof HTMLElement)) return;
const choice = t.getAttribute("data-theme-set");
if (!choice) return;
safeSet(choice);
applyTheme(choice);
});
}
/* boot deterministico */
applyTheme(safeGet() || "system");
bindUI();
})();
🧪 Debug essenziale: se “non cambia niente”, dove guardare subito
- 1) CSS caricato? Se theme.css non è caricato, non vedi nessun effetto: controlla Network o sorgente pagina.
- 2) JS caricato? Se theme.js non gira, resta solo il fallback. Apri Console: errori di path o sintassi si vedono subito.
- 3) Token coerenti? Se nel CSS usi --bg ma nel JS scrivi --bg-color, non cambia nulla.
- 4) Cache? Se aggiorni theme.js ma il browser usa la versione vecchia, sembra “rotto”. Forza refresh o disabilita cache in DevTools.
🔍 Tesi falsificabile e criterio di falsificazione
Tesi: se tutte le regole CSS dipendono da token e i temi sono definiti come JSON di token→valore applicati a :root, allora aggiungere un tema o correggere un colore richiede modificare solo il JSON (non il CSS) e il sito resta leggibile anche senza JavaScript grazie al fallback su preferenza di sistema.
Criterio di falsificazione: se per aggiungere/correggere un tema sei costretto a modificare più punti del CSS (valori hardcoded o regole duplicate), oppure se senza JS la pagina diventa illeggibile, allora il modello è incompleto o implementato male: non hai tokenizzato davvero o hai eliminato il fallback.
🌐 Implicazioni e connessioni interdisciplinari
Ingegneria del software: separare dati (temi) da logica (regole CSS) riduce duplicazione e drift. È un principio verificabile: meno punti di modifica per un cambio, meno divergenze.
HCI e accessibilità: tema significa leggibilità e contrasto. La preferenza OS è un segnale, ma non garantisce che il tuo contrasto sia adeguato: i token devono includere stati (hover/focus) e superfici, non solo sfondo/testo.
Operatività editoriale: un sito di contenuti cresce. Token e temi come dati permettono evoluzione del design senza “rompere” l’archivio: è manutenzione sistemica, non estetica.
❓ Limiti, incertezze, domande aperte
Limite: l’assenza totale di flash non si dichiara senza misure sul progetto reale; qui si riduce il problema con fallback CSS e boot immediato.
Incertezza: storage e caching possono comportarsi diversamente per browser e impostazioni privacy; il sistema deve degradare al fallback.
Domanda aperta: quanti token servono davvero? La risposta dipende dal numero di componenti e dalla complessità del sito: è un equilibrio empirico tra granularità e manutenibilità.
🧾 Sintesi finale (non prescrittiva)
Il dynamic theme switching con JSON non è una “feature da demo”: è una disciplina architetturale. Funziona quando il CSS usa token ovunque, il tema è un set di valori coerenti (JSON), e il JavaScript applica questi valori in modo deterministico con fallback sensato. La qualità della soluzione non sta nel numero di temi, ma nel fatto che il sistema resti governabile: un cambio di tema deve essere un cambio di dati, non una riscrittura di regole.
🧪 Demo interattivo (sandbox di test)
Questa demo è un sandbox controllato: testa token e logica di switch senza dipendere dal CSS globale del sito.