डेवलपर्स को जल्दी काम निपटाना पसंद है। जब टिकट में "डार्क मोड जोड़ें" लिखा होता है, तो सबसे आसान रास्ता साफ दिखता है: light.css लिखें, dark.css लिखें, और उनके बीच टॉगल करें। यह साफ-सुथरा लगता है। यह जल्दी शिप हो जाता है। तीन कंपोनेंट्स वाले एक छोटे साइड प्रोजेक्ट के लिए, यह शायद काम कर जाए। लेकिन जैसे ही आपका एप्लिकेशन कुछ मॉड्यूल्स से आगे बढ़ता है, वह दूसरी फ़ाइल एक संपत्ति (asset) नहीं रह जाती, बल्कि एक बोझ बन जाती है जिसे आपको डुप्लिकेट रूप में मेंटेन करना पड़ता है।

दो-फ़ाइल वाला जाल (The Two-File Trap)

पहली नज़र में तर्क सही लगता है। कार्यों का पृथक्करण (Separation of concerns), है ना? लाइट चीज़ें यहाँ, डार्क चीज़ें वहाँ। आप अपने एडिटर में दो बफ़र्स खोलते हैं। आप लाइट फ़ाइल से कार्ड स्टाइल्स को डार्क फ़ाइल में कॉपी करते हैं, #ffffff को #1a1a1a से बदलते हैं, और काम खत्म।

समस्या पहले हफ्ते में नहीं आती। समस्या छठे महीने में आती है, जब एक डिज़ाइनर प्राइमरी बटन पर थोड़ा अलग बॉर्डर रेडियस मांगता है, या जब प्रोडक्ट टीम चेकआउट फॉर्म पर एक नया वार्निंग स्टेट चाहती है। आप लाइट स्टाइलशीट को अपडेट करते हैं। आप डार्क स्टाइलशीट को बस ऊपर-ऊपर से देख लेते हैं। शायद आपको बदलाव कॉपी करना याद रहे। शायद न रहे। वही अंतर है जहाँ क्वालिटी खत्म हो जाती है। अब आप एक इंटरफ़ेस को मेंटेन नहीं कर रहे हैं। आप दो समानांतर (parallel) इंटरफ़ेस मेंटेन कर रहे हैं जो केवल एक ही HTML स्केलेटन साझा करते हैं।

थीम ड्रिफ्ट (Theme Drift) अपरिहार्य है

इस अंतर का एक नाम है जिसे फ्रंटएंड टीमें अब पहचानने लगी हैं: थीम ड्रिफ्ट (theme drift)। यह तब होता है जब आपकी दो स्टाइलशीट अलग-अलग गति से विकसित होती हैं। यहाँ पैडिंग एडजस्टमेंट, वहाँ शैडो में बदलाव। डार्क फ़ाइल उपेक्षित (neglected) हो जाती है। या इससे भी बुरा, यह डर का कारण बन जाती है। डेवलपर्स बदलावों से बचने लगते हैं क्योंकि एक थीम को छूने का मतलब है काम को डुप्लिकेट करने के लिए दूसरी फ़ाइल में ढूँढना।

संज्ञानात्मक बोझ (Cognitive overhead) तेज़ी से बढ़ता है। आप CSS एक बार लिखना चाहते थे। इसके बजाय आपने इसे दो बार लिखा, और अब आप हर बार डिज़ाइन सिस्टम बदलने पर उस कर्ज पर ब्याज चुका रहे हैं। डार्क मोड में आइकन मिसअलाइन हो जाते हैं क्योंकि किसी ने लाइट फ़ाइल में flex gap अपडेट किया और उसे मिरर करना भूल गया। फोकस रिंग गायब हो जाते हैं क्योंकि एक नया एक्सेसिबिलिटी नियम केवल एक शीट में ही शामिल किया गया था। UI सिर्फ गलत नहीं दिखता। यह टूटा हुआ महसूस होने लगता है।

सिमेंटिक टोकन (Semantic Tokens) का उपयोग करें

इसका समाधान कोई बेहतर 'diff tool' या सख्त कोड रिव्यू नहीं है। समाधान रंग के बारे में सोचने का एक अलग तरीका है। अपने स्टाइल्स को उनके वास्तविक रूप (literal appearance) के आधार पर व्यवस्थित करना बंद करें और उन्हें उनके उद्देश्य (purpose) के आधार पर व्यवस्थित करना शुरू करें। यहीं सिमेंटिक टोकन काम आते हैं।

किसी कार्ड को सफेद बैकग्राउंड देने के बजाय, उसे surface background दें। टेक्स्ट के लिए ब्लैक और ऑफ-व्हाइट के बीच चुनने के बजाय, एक text color चुनें। कंपोनेंट को यह नहीं पता होता या इससे फर्क नहीं पड़ता कि यूजर लाइट मोड पसंद करता है या डार्क। वह बस उस टोकन को मांगता है जो उसके काम से मेल खाता हो।

एक स्टैंडर्ड बटन के बारे में सोचें। दो-फ़ाइल वाली दुनिया में, .btn लाइट स्टाइलशीट में रहता है जिसका बैकग्राउंड सफेद और बॉर्डर डार्क होता है। उसका जुड़वां डार्क स्टाइलशीट में रहता है जिसका बैकग्राउंड लगभग काला और बॉर्डर हल्का होता है। एक बटन के लिए यह दोगुना कोड है। टोकन के साथ, .btn का केवल एक डिक्लेरेशन होता है: बैकग्राउंड var(--color-surface-secondary) है और बॉर्डर var(--color-border-default) है। वैल्यूज़ खुद रूट (root) पर रहती हैं। जब साइट लाइट मोड में होती है, तो --color-surface-secondary का मान #f8f9fa जैसा कुछ होता है। डार्क मोड में, वही टोकन #2d2d2d हो जाता है। बटन कंपोनेंट कभी नहीं बदलता। केवल उसके नीचे का डेटा बदलता है।

स्ट्रक्चर और डेटा के बीच का यह अंतर सूक्ष्म है लेकिन शक्तिशाली है। आपका कार्ड कंपोनेंट लेआउट, स्पेसिंग, टाइपोग्राफी और एलिवेशन को एक बार परिभाषित करता है। आपका थीम लेयर पैलेट (palette) को परिभाषित करता है। यही वह पृथक्करण है जिसके लिए CSS custom properties बनाई गई थीं।

आर्किटेक्चर कैसे बदलता है

यह दृष्टिकोण मौलिक रूप से आपके स्टाइल लिखने के तरीके को पुनर्गठित करता है।

पुराना तरीका आमतौर पर ऐसा दिखता है:

  • एक लाइट कार्ड स्टाइलशीट जो पैडिंग, रेडियस, बैकग्राउंड, टेक्स्ट कलर और शैडो को परिभाषित करती है।
  • एक डार्क कार्ड स्टाइलशीट जो रंगों को बदलने के लिए उन्हीं में से अधिकांश प्रॉपर्टीज को फिर से परिभाषित करती है।
  • एक लॉजिक लेयर जो तय करती है कि कौन सी स्टाइलशीट लोड करनी है या बॉडी पर कौन सी क्लास टॉगल करनी है।

नया तरीका ऐसा दिखता है:

  • एक कार्ड स्टाइलशीट जो लेआउट को परिभाषित करती है और सिमेंटिक टोकन असाइन करती है।
  • एक थीम फ़ाइल जो परिभाषित करती है कि लाइट कॉन्टेक्स्ट में उन टोकन का क्या अर्थ है।
  • एक थीम फ़ाइल, या उसी फ़ाइल में एक ब्लॉक, जो परिभाषित करती है कि डार्क कॉन्टेक्स्ट में उन टोकन का क्या अर्थ है।
  • एक सिंगल एट्रिब्यूट स्वैप जो कंपोनेंट लेयर को छुए बिना वैल्यू लेयर को बदल देता है।

आप सेटअप को स्थिर रखते हैं। आप केवल डेटा बदलते हैं। जब डिज़ाइनर एक तीसरा थीम पेश करना चाहता है, जैसे कि हाई-कॉन्ट्रास्ट मोड या मिडनाइट ब्लू वेरिएंट, तो आपको कार्ड को फिर से लिखने की ज़रूरत नहीं होती। आप बस टोकन मैप में एक और असाइनमेंट जोड़ देते हैं। कंपोनेंट सरल और सुव्यवस्थित बना रहता है। उसे अभी भी एक सरफेस कलर चाहिए होता है। थीम उसे बताती है कि किस सरफेस कलर का उपयोग करना है।

डेटा एट्रिब्यूट स्विच

इम्प्लीमेंटेशन सरल और पठनीय रह सकता है। अपने HTML टैग पर एक डेटा एट्रिब्यूट लागू करें, जैसे कि data-theme="dark", और अपने टोकन डेफिनिशन को इसके अंतर्गत स्कोप करें।

लाइट अनुभव के लिए :root पर अपने डिफ़ॉल्ट्स सेट करें ताकि JavaScript चलने से पहले पेज सही ढंग से रेंडर हो सके। फिर [data-theme="dark"] के अंतर्गत टोकन वैल्यूज़ को ओवरराइड करें। एक छोटा सा स्क्रिप्ट टॉगल क्लिक पर नज़र रखता है, एट्रिब्यूट को अपडेट करता है, और पेज का हर कंपोनेंट तुरंत प्रतिक्रिया देता है। व्यक्तिगत तत्वों पर कोई क्लास थ्रैशिंग नहीं। रेंडरिंग के बीच में पूरी तरह से अलग स्टाइलशीट इम्पोर्ट करने की ज़रूरत नहीं। ब्राउज़र के पास वेरिएबल्स पहले से ही मेमोरी में होते हैं; यह बस नई वैल्यूज़ के साथ रीपेंट करता है।

यह आपके कोड को बहुत व्यावहारिक रूप से साफ रखता है। आपको .card के हर इंस्टेंस को खोजने के लिए दो डायरेक्टरीज़ में grep करने की ज़रूरत नहीं है। आपको एक ही नोड पर स्टैक्ड प्रतिस्पर्धी थीम क्लासेस के बीच स्पेसिफिसिटी वॉर्स की चिंता करने की ज़रूरत नहीं है। आपका HTML पठनीय रहता है। आपका CSS सेंट्रलाइज्ड और सर्च करने योग्य बना रहता है।

यह वैल्यूज़ के बारे में है, वर्शन्स के बारे में नहीं

डार्क मोड वैल्यूज़ के बारे में है। यह आपके UI का दूसरा वर्शन नहीं है। रात में आपके कार्ड के कोने ज़्यादा गोल नहीं हो जाते। आपका ग्रिड किसी अलग आकार में नहीं बदलता। आपके टाइप स्केल को एक नए रिदम की ज़रूरत नहीं होती। केवल रंग बदलते हैं, और कभी-कभी शैडो थोड़ी गहरी हो जाती है। डार्क मोड को पूरी तरह से 'रीस्किन' के रूप में मानना एक ओवर-इंजीनियरिंग है जो मेंटेनेंस की मुश्किलें पैदा करती है।

जो टीमें इसे सही ढंग से करती हैं, वे अपने डिज़ाइन सिस्टम को एक डेटाबेस की तरह मानती हैं। कंपोनेंट्स नाम के आधार पर प्रॉपर्टीज़ के लिए क्वेरी करते हैं। थीम्स रिकॉर्ड प्रदान करती हैं। लाइट से डार्क में स्विच करना एक क्वेरी पैरामीटर बदलाव है, न कि स्कीमा रीराइट।

यही मानसिकता आपको थीम ड्रिफ्ट से बचाती है। एक कार्ड। एक बटन। स्पेसिंग और साइजिंग के लिए एक ही सोर्स ऑफ़ ट्रुथ। पैलेट एक ही जगह रहता है, लॉजिकली मैप किया हुआ, और उपयोगकर्ता जो भी एनवायरनमेंट पसंद करे उसके लिए तैयार।

असली निष्कर्ष

यदि आप लाइट और डार्क के लिए दो CSS फ़ाइलें मेंटेन कर रहे हैं, तो आप थीमिंग नहीं कर रहे हैं। आप क्लोनिंग कर रहे हैं। सिमेंटिक टोकन पर जाएँ, उन्हें रूट-लेवल डेटा एट्रिब्यूट के साथ स्कोप करें, और अपने कंपोनेंट्स को अपियरेंस को हार्डकोड करने के बजाय रोल्स (roles) माँगने दें। शुरुआती रिफैक्टर में मेहनत लगती है, लेकिन इसका विकल्प पैरेलल स्टाइलशीट्स के बीच एक अंतहीन संघर्ष है। एक ही कार्ड को दो बार लिखने के लिए जीवन बहुत छोटा है।