Implementare il dark mode in un sito web non richiede librerie esterne né framework pesanti. In questa guida pratica vediamo come costruire un toggle tema completo utilizzando solo variabili CSS e un piccolo script JavaScript, con persistenza della preferenza utente tramite localStorage e rispetto delle impostazioni di sistema.
L’approccio che proponiamo è vanilla, performante e accessibile: zero dipendenze, caricamento istantaneo e nessun flash di contenuto non stilizzato (FOUC).
Perché Implementare il Dark Mode nel 2026
Il dark mode non è più una semplice tendenza estetica. Oggi rappresenta uno standard di usabilità che gli utenti si aspettano. I vantaggi concreti sono:
- Riduzione dell’affaticamento visivo in condizioni di scarsa illuminazione
- Risparmio energetico su schermi OLED e AMOLED
- Miglior accessibilità per utenti con sensibilità alla luce
- Coerenza con il sistema operativo dell’utente
- Maggiore engagement: gli utenti restano più a lungo su siti che rispettano le loro preferenze

Le Tre Strade per il Dark Mode in CSS
Prima di scrivere codice, capiamo le opzioni disponibili nel panorama attuale:
| Metodo | Pro | Contro |
|---|---|---|
prefers-color-scheme |
Automatico, segue il sistema | L’utente non può forzare un tema diverso |
Funzione light-dark() |
Sintassi compatta e moderna | Supporto browser più recente |
| Variabili CSS + attributo data-* | Massima flessibilità, toggle manuale | Richiede JavaScript |
La soluzione che illustriamo combina i tre approcci per ottenere il meglio: rispetto della preferenza di sistema come default, possibilità di override manuale e codice manutenibile.
Step 1: Definire le Variabili CSS
Il fondamento di tutto è una buona architettura di custom properties. Nominale i colori in modo semantico (non per il colore in sé) così potrai cambiarli in base al tema senza riscrivere nulla.
:root {
color-scheme: light dark;
--bg-primary: #ffffff;
--bg-secondary: #f4f4f5;
--text-primary: #18181b;
--text-secondary: #52525b;
--accent: #0066ff;
--border: #e4e4e7;
}
[data-theme="dark"] {
--bg-primary: #0a0a0a;
--bg-secondary: #1a1a1a;
--text-primary: #fafafa;
--text-secondary: #a1a1aa;
--accent: #4d9fff;
--border: #27272a;
}
body {
background-color: var(--bg-primary);
color: var(--text-primary);
transition: background-color 0.2s ease, color 0.2s ease;
}
Nota due dettagli importanti:
- color-scheme: light dark; dice al browser di adattare anche gli elementi nativi (scrollbar, input, form)
- L’attributo data-theme sull’elemento
htmlsarà controllato da JavaScript

Step 2: Il Markup del Toggle
Per garantire accessibilità, usiamo un button con stati ARIA appropriati:
<button
id="theme-toggle"
type="button"
aria-label="Cambia tema"
aria-pressed="false">
<span class="icon-sun">☀</span>
<span class="icon-moon">☾</span>
</button>
L’attributo aria-pressed comunica agli screen reader lo stato attuale del pulsante.
Step 3: Lo Script JavaScript
Ecco il cuore della logica. Lo script gestisce tre scenari: preferenza salvata, preferenza di sistema, toggle manuale.
(function() {
const STORAGE_KEY = 'theme-preference';
const root = document.documentElement;
function getPreference() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) return stored;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
function applyTheme(theme) {
root.setAttribute('data-theme', theme);
const btn = document.getElementById('theme-toggle');
if (btn) {
btn.setAttribute('aria-pressed', theme === 'dark');
}
}
// Applica subito per evitare flash
let currentTheme = getPreference();
applyTheme(currentTheme);
document.addEventListener('DOMContentLoaded', function() {
const toggle = document.getElementById('theme-toggle');
if (!toggle) return;
toggle.addEventListener('click', function() {
currentTheme = currentTheme === 'dark' ? 'light' : 'dark';
localStorage.setItem(STORAGE_KEY, currentTheme);
applyTheme(currentTheme);
});
});
// Aggiorna se cambia il tema di sistema
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', function(e) {
if (!localStorage.getItem(STORAGE_KEY)) {
currentTheme = e.matches ? 'dark' : 'light';
applyTheme(currentTheme);
}
});
})();
Evitare il Flash of Unstyled Content
Per impedire che la pagina appaia un istante con il tema sbagliato, inserisci lo script nel head del documento, prima di qualsiasi CSS che dipende dal tema, e senza attributo defer o async. Lo script è minimo e si esegue in millisecondi.

Step 4: Stilizzare il Pulsante Toggle
Mostra l’icona corretta in base al tema attivo:
#theme-toggle {
background: var(--bg-secondary);
border: 1px solid var(--border);
color: var(--text-primary);
padding: 0.5rem 0.75rem;
border-radius: 8px;
cursor: pointer;
}
.icon-moon { display: none; }
[data-theme="dark"] .icon-sun { display: none; }
[data-theme="dark"] .icon-moon { display: inline; }
Checklist di Accessibilità
Un buon dark mode è anche accessibile. Verifica questi punti prima di pubblicare:
- Il contrasto WCAG AA è rispettato in entrambi i temi (minimo 4.5:1 per il testo normale)
- Il pulsante toggle è raggiungibile via tastiera
- Lo stato è comunicato tramite
aria-pressedoaria-label - Le transizioni rispettano
prefers-reduced-motion - Immagini e icone restano visibili e leggibili in dark mode
Per il punto sulle animazioni ridotte, aggiungi:
@media (prefers-reduced-motion: reduce) {
body {
transition: none;
}
}

Bonus: la Funzione light-dark() Moderna
Se vuoi sperimentare con la sintassi più recente supportata da tutti i browser moderni, puoi semplificare alcune regole:
:root {
color-scheme: light dark;
--bg-primary: light-dark(#ffffff, #0a0a0a);
--text-primary: light-dark(#18181b, #fafafa);
}
Questa funzione restituisce automaticamente il primo o il secondo valore in base allo schema attivo. Resta comunque utile abbinarla all’attributo data-theme per il toggle manuale.
Errori Comuni da Evitare
- Usare colori puri come
#000000per lo sfondo dark: causa affaticamento. Meglio un nero leggermente più caldo come#0a0a0a - Dimenticare le immagini: alcune immagini bianche “sparano” troppo in dark mode. Considera filtri o varianti specifiche
- Non testare i form: input, select e textarea hanno stili nativi che vanno gestiti
- Caricare lo script in fondo alla pagina: causa il flash bianco all’apertura
- Nominare le variabili per colore (es.
--blu) invece che per funzione (es.--accent)
FAQ sul Dark Mode con CSS e JavaScript
Serve davvero JavaScript per il dark mode?
No, se ti basta seguire la preferenza di sistema puoi usare solo prefers-color-scheme in CSS. JavaScript serve quando vuoi offrire all’utente un toggle manuale e salvarne la scelta.
Posso usare localStorage in modo sicuro?
Sì, per una preferenza di tema è perfetto. È sincrono, persistente e non richiede consenso GDPR se usato esclusivamente per preferenze di interfaccia funzionali al sito.
Come gestisco le immagini in dark mode?
Tre opzioni: usare <picture> con sorgenti diverse via media query, applicare filtri CSS come filter: invert(1) a logo specifici, o servire SVG con colori basati su currentColor.
Il dark mode influisce sulla SEO?
Direttamente no, ma indirettamente sì: migliora tempo di permanenza, riduce la frequenza di rimbalzo e segnala attenzione all’esperienza utente, tutti fattori che Google considera.
Devo usare un framework come Tailwind per il dark mode?
Non è necessario. La soluzione vanilla mostrata in questo articolo è più leggera (meno di 1KB di JavaScript) e non aggiunge dipendenze al progetto.
Conclusione
Implementare un dark mode performante con CSS e JavaScript richiede meno di 50 righe di codice se imposti bene l’architettura. Le variabili CSS semantiche, un attributo data-theme sul root e uno script minimale per il toggle sono tutto ciò che serve.
Questo approccio è scalabile: funziona su un sito statico, su WordPress, su un’applicazione complessa. Senza framework, senza bundle pesanti, senza dipendenze. Solo standard web.
Se hai bisogno di integrare il dark mode in un progetto esistente o stai progettando da zero un sito moderno, il team di Alede Ro Design può aiutarti a strutturare il tuo sistema di temi in modo professionale e accessibile.