I developer amano i risultati immediati. Quando il ticket dice "aggiungi la dark mode", la via più semplice sembra ovvia: scrivi light.css, scrivi dark.css e passa dall'uno all'altro. Sembra pulito. Viene rilasciato velocemente. Per un piccolo progetto con tre componenti, potrebbe persino funzionare. Ma una volta che l'applicazione cresce oltre una manciata di moduli, quel secondo file smette di essere un vantaggio e diventa un peso da mantenere in duplicato.
La trappola dei due file
La logica sembra valida a prima vista. Separazione delle responsabilità, giusto? Le cose chiare qui, quelle scure là. Apri due buffer nel tuo editor. Copi gli stili della card dal file light al file dark, sostituisci #ffffff con #1a1a1a e hai finito.
Il problema non si presenta nella prima settimana. Il problema è il sesto mese, quando un designer chiede un border radius leggermente diverso sul pulsante principale, o quando il team di prodotto vuole un nuovo stato di avviso nel modulo di checkout. Aggiorni il foglio di stile light. Dai un'occhiata veloce a quello dark. Forse ti ricordi di copiare la modifica. Forse no. È in quel divario che la qualità muore. Non stai più mantenendo un'unica interfaccia. Stai mantenendo due interfacce parallele che per caso condividono lo stesso scheletro HTML.
Il theme drift è inevitabile
Questo divario ha un nome che i team frontend stanno iniziando a riconoscere: theme drift. Accade quando i tuoi due fogli di stile evolvono a velocità diverse. Un aggiustamento del padding qui. Una modifica all'ombra lì. Il file dark diventa il fratello trascurato. O peggio, diventa una fonte di timore. Gli sviluppatori iniziano a evitare le modifiche perché toccare un tema significa dover cercare in un altro file per duplicare il lavoro.
Il carico cognitivo aumenta rapidamente. Volevi scrivere il CSS una volta sola. Invece lo hai scritto due volte, e ora stai pagando gli interessi su quel debito ogni volta che il design system cambia. Le icone si disallineano in dark mode perché qualcuno ha aggiornato un flex gap nel file light e si è dimenticato di replicarlo. Gli anelli di focus (focus rings) scompaiono perché una nuova regola di accessibilità è finita solo in un foglio di stile. L'interfaccia utente non sembra solo sbagliata. Inizia a sembrare rotta.
Usa i token semantici
La soluzione non è un miglior tool di diff o una revisione del codice più rigorosa. La soluzione è un modo diverso di pensare al colore. Smetti di organizzare i tuoi stili in base all'aspetto letterale e inizia a organizzarli in base allo scopo. È qui che entrano in gioco i token semantici.
Invece di assegnare a una card uno sfondo bianco, assegnale uno sfondo di tipo "surface". Invece di scegliere tra nero e bianco sporco per il testo, scegli un colore per il testo. Il componente non sa e non gli importa se l'utente preferisce la modalità light o dark. Chiede semplicemente il token che corrisponde al suo compito.
Pensa a un pulsante standard. In un mondo a due file, .btn vive nel foglio di stile light con uno sfondo bianco e un bordo scuro. Il suo gemello vive nel foglio di stile dark con uno sfondo quasi nero e un bordo più chiaro. Questo significa il doppio del codice per un solo pulsante. Con i token, .btn ha una sola dichiarazione: lo sfondo è var(--color-surface-secondary) e il bordo è var(--color-border-default). I valori stessi risiedono alla radice (root). Quando il sito è in modalità light, --color-surface-secondary si risolve in qualcosa come #f8f9fa. In modalità dark, lo stesso token si risolve in #2d2d2d. Il componente del pulsante non cambia mai. Cambiano solo i dati sottostanti.
Questa distinzione tra struttura e dati è sottile ma potente. Il tuo componente card definisce layout, spaziatura, tipografia ed elevazione una volta sola. Il tuo livello tematico definisce la palette. Questa separazione è esattamente ciò per cui sono state create le proprietà personalizzate CSS.
Come cambia l'architettura
Questo approccio ristruttura fondamentalmente il modo in cui scrivi gli stili.
Il vecchio modo di solito è questo:
- Un foglio di stile per la card light che definisce padding, raggio, sfondo, colore del testo e ombra.
- Un foglio di stile per la card dark che ridefinisce la maggior parte delle stesse proprietà solo per invertire i colori.
- Un livello logico che decide quale foglio di stile caricare o quale classe attivare sul body.
Il nuovo modo è questo:
- Un foglio di stile per la card che definisce il layout e assegna i token semantici.
- Un file tematico che definisce il significato di quei token in un contesto light.
- Un file tematico, o semplicemente un blocco nello stesso file, che definisce il significato di quei token in un contesto dark.
- Un singolo scambio di attributi che cambia il livello dei valori senza toccare il livello del componente.
You keep the setup stable. You only change the data. When the designer wants to introduce a third theme, maybe a high-contrast mode or a midnight blue variant, you do not rewrite the card. You add one more assignment to the token map. The component stays dumb and happy. It still wants a surface color. The theme tells it which surface color to use.
The Data Attribute Switch
Implementation can stay simple and readable. Apply a data attribute to your HTML tag, something like data-theme="dark", and let your token definitions scope under it.
Set your defaults on :root for the light experience so the page renders correctly before JavaScript runs. Then override the token values under [data-theme="dark"]. A tiny script watches for a toggle click, updates the attribute, and every component on the page responds instantly. No class thrashing on individual elements. No importing an entirely separate stylesheet mid-render. The browser already has the variables in memory; it just repaints with new values.
This keeps your code clean in a very practical sense. You do not have to grep across two directories to find every instance of .card. You do not have to worry about specificity wars between competing theme classes stacked on the same node. Your HTML stays readable. Your CSS stays centralized and searchable.
It Is About Values, Not Versions
Dark mode is about values. It is not a second version of your UI. The corners of your card do not get rounder at night. Your grid does not collapse into a different shape. Your type scale does not need a new rhythm. Only the colors shift, and sometimes the shadows breathe a little deeper. Treating darkness as a full reskin is over-engineering that creates maintenance nightmares.
The teams that get this right treat their design system like a database. Components query for properties by name. Themes provide the records. Switching from light to dark is a query parameter change, not a schema rewrite.
That mindset is what saves you from theme drift. One card. One button. One source of truth for spacing and sizing. The palette lives in one place, logically mapped, ready for whatever environment the user prefers.
The Real Takeaway
If you are maintaining two CSS files for light and dark, you are not theming. You are cloning. Move to semantic tokens, scope them with a root-level data attribute, and let your components ask for roles instead of hardcoding appearances. The initial refactor takes effort, but the alternative is an endless game of whack-a-mole across parallel stylesheets. Life is too short to write the same card twice.
