डेव्हलपर्सना झटपट यश मिळवणे आवडते. जेव्हा तिकीटमध्ये "डार्क मोड जोडा" असे म्हटले जाते, तेव्हा सर्वात सोपा मार्ग स्पष्ट दिसतो: light.css लिहा, dark.css लिहा आणि त्यांच्यामध्ये टॉगल करा. हे स्वच्छ वाटते. ते वेगाने पूर्ण होते. तीन कंपोनंट्स असलेल्या छोट्या साईड प्रोजेक्टसाठी हे कदाचित टिकून राहू शकेल. पण एकदा का तुमचे ॲप्लिकेशन काही मॉड्यूल्सच्या पलीकडे वाढले की, ती दुसरी फाईल मालमत्ता (asset) न राहता एक ओझे (liability) बनते, ज्याची तुम्हाला दुप्पट देखभाल करावी लागते.
दोन-फाईल्सचा सापळा (The Two-File Trap)
पहिल्या नजरेत हे तर्क योग्य वाटते. 'सेपरेशन ऑफ कन्सेर्न्स' (Separation of concerns), बरोबर ना? हलक्या रंगाच्या गोष्टी इथे, गडद रंगाच्या गोष्टी तिथे. तुम्ही तुमच्या एडिटरमध्ये दोन बफर्स उघडता. तुम्ही लाईट फाईलमधून कार्ड स्टाइल्स डार्क फाईलमध्ये कॉपी करता, #ffffff च्या जागी #1a1a1a वापरता आणि काम संपवता.
समस्या पहिल्या आठवड्यात नसते. समस्या सहाव्या महिन्यात येते, जेव्हा एखादा डिझायनर प्रायमरी बटणवर थोडा वेगळा 'बॉर्डर रेडियस' (border radius) मागतो, किंवा जेव्हा प्रॉडक्ट टीम चेकआउट फॉर्मवर नवीन 'वॉर्निंग स्टेट' (warning state) हवी असल्याचे सांगते. तुम्ही लाईट स्टाईलशीट अपडेट करता. तुम्ही डार्क स्टाईलशीटवर फक्त नजर टाकता. कदाचित तुम्हाला तो बदल कॉपी करायचे आठवेल. कदाचित आठवणार नाही. याच त्रुटीमुळे गुणवत्ता कमी होते. तुम्ही आता एक इंटरफेस मेंटेन करत नाही आहात. तुम्ही दोन समांतर इंटरफेस मेंटेन करत आहात जे फक्त एकाच HTML स्केलेटनचा वापर करत आहेत.
थीम ड्रिफ्ट (Theme Drift) अपरिहार्य आहे
या त्रुटीला एक नाव आहे जे फ्रंटएंड टीम्स आता ओळखू लागली आहेत: थीम ड्रिफ्ट (theme drift). जेव्हा तुमच्या दोन स्टाईलशीट्स वेगवेगळ्या वेगाने विकसित होतात, तेव्हा हे घडते. इथे पॅडिंगमध्ये बदल, तिथे शॅडोमध्ये थोडा बदल. डार्क फाईल दुर्लक्षित ठरते. किंवा त्याहून वाईट म्हणजे, ती भीतीचे कारण बनते. डेव्हलपर्स बदल करणे टाळू लागतात कारण एका थीममध्ये बदल करणे म्हणजे काम दुप्पट करण्यासाठी दुसऱ्या फाईलमध्ये शोध घेणे होय.
यामुळे होणारा मानसिक ताण (cognitive overhead) वेगाने वाढतो. तुम्हाला CSS एकदाच लिहायचा होता. त्याऐवजी तुम्ही तो दोनदा लिहिला, आणि आता प्रत्येक वेळी डिझाइन सिस्टम बदलली की तुम्हाला त्या कर्जाचे व्याज भरावे लागत आहे. डार्क मोडमध्ये आयकॉन्स अलाइन होत नाहीत कारण कोणीतरी लाईट फाईलमध्ये flex gap अपडेट केले आणि ते डार्क फाईलमध्येही करणे विसरले. नवीन ॲक्सेसिबिलिटी (accessibility) नियम फक्त एकाच शीटमध्ये असल्याने फोकस रिंग्स गायब होतात. UI फक्त चुकीचे दिसत नाही, तर ते बिघडल्यासारखे वाटते.
सिमँटिक टोकन्सचा (Semantic Tokens) वापर करा
उपाय म्हणजे एखादे चांगले 'डिफ टूल' (diff tool) किंवा कडक 'कोड रिव्ह्यू' (code review) नाही. उपाय म्हणजे रंगाकडे पाहण्याचा एक वेगळा दृष्टिकोन. तुमच्या स्टाइल्स केवळ दिसण्यावरून न मांडता, त्यांच्या उद्देशानुसार मांडायला सुरुवात करा. इथेच सिमँटिक टोकन्सची भूमिका येते.
कार्डला पांढरा बॅकग्राउंड देण्याऐवजी, त्याला 'सरफेस बॅकग्राउंड' (surface background) द्या. मजकुरासाठी काळा किंवा ऑफ-व्हाइट निवडण्याऐवजी, 'टेक्स्ट कलर' (text color) निवडा. वापरकर्त्याला लाईट मोड आवडतो की डार्क, याने कंपोनंटला काहीही फरक पडत नाही. तो फक्त त्याच्या कामाशी जुळणारा टोकन मागतो.
एका सामान्य बटणाबद्दल विचार करा. दोन-फाईल्सच्या जगात, .btn लाईट स्टाईलशीटमध्ये पांढरा बॅकग्राउंड आणि गडद बॉर्डरसह असते. तिची दुसरी आवृत्ती डार्क स्टाईलशीटमध्ये जवळजवळ काळ्या बॅकग्राउंडसह आणि हलक्या बॉर्डरसह असते. एका बटणासाठी हा दुप्पट कोड आहे. टोकन्ससह, .btn मध्ये एकच डिक्लेरेशन असते: बॅकग्राउंड var(--color-surface-secondary) आहे आणि बॉर्डर var(--color-border-default) आहे. ही व्हॅल्यूज रूटवर (root) असतात. जेव्हा साइट लाईट मोडमध्ये असते, तेव्हा --color-surface-secondary चे मूल्य #f8f9fa सारखे काहीतरी असते. डार्क मोडमध्ये, तोच टोकन #2d2d2d असा बदलतो. बटण कंपोनंट कधीच बदलत नाही, फक्त त्याखालील डेटा बदलतो.
स्ट्रक्चर आणि डेटा मधील हा फरक सूक्ष्म आहे पण शक्तिशाली आहे. तुमचा कार्ड कंपोनंट लेआउट, स्पेसिंग, टायपोग्राफी आणि एलिव्हेशन (elevation) एकदाच परिभाषित करतो. तुमचे थीम लेयर पॅलेट (palette) परिभाषित करते. CSS कस्टम प्रॉपर्टीज (custom properties) नेमक्या याच कारणासाठी बनवल्या गेल्या आहेत.
आर्किटेक्चरमध्ये कसे बदल होतात
हा दृष्टिकोन तुम्ही स्टाइल्स कशा लिहिता याचे मूलभूत पुनर्रचना करतो.
जुनी पद्धत साधारणपणे अशी असते:
- एक लाईट कार्ड स्टाईलशीट जी पॅडिंग, रेडियस, बॅकग्राउंड, टेक्स्ट कलर आणि शॅडो परिभाषित करते.
- एक डार्क कार्ड स्टाईलशीट जी फक्त रंग बदलण्यासाठी त्याच प्रॉपर्टीज पुन्हा परिभाषित करते.
- एक लॉजिक लेयर जो ठरवतो की कोणती स्टाईलशीट लोड करायची किंवा बॉडीवर कोणता क्लास टॉगल करायचा.
नवीन पद्धत अशी दिसते:
- एक कार्ड स्टाईलशीट जी लेआउट परिभाषित करते आणि सिमँटिक टोकन्स नियुक्त करते.
- एक थीम फाईल जी लाईट कॉन्टेक्स्टमध्ये त्या टोकन्सचा अर्थ काय आहे हे सांगते.
- एक थीम फाईल, किंवा त्याच फाईलमधील एक ब्लॉक, जो डार्क कॉन्टेक्स्टमध्ये त्या टोकन्सचा अर्थ काय आहे हे सांगतो.
- एक सिंगल ॲट्रिब्युट स्वॅप (attribute swap) जो कंपोनंट लेयरला स्पर्श न करता व्हॅल्यू लेयर बदलतो.
तुम्ही सेटअप स्थिर ठेवता. तुम्ही फक्त डेटा बदलता. जेव्हा डिझायनरला तिसरा थीम सादर करायचा असतो, कदाचित हाय-कॉन्ट्रास्ट मोड किंवा मिडनाईट ब्लू व्हेरिएंट, तेव्हा तुम्हाला कार्ड पुन्हा लिहावे लागत नाही. तुम्ही टोकन मॅपमध्ये फक्त एक नवीन असाइनमेंट जोडता. कंपोनंट साधे आणि आनंदी राहते. त्याला अजूनही सरफेस कलर हवा असतो. थीम त्याला कोणता सरफेस कलर वापरायचा हे सांगते.
डेटा ॲट्रिब्यूट स्विच (The Data Attribute Switch)
अंमलबजावणी (Implementation) सोपी आणि वाचनीय राहू शकते. तुमच्या HTML टॅगला data-theme="dark" सारखा एखादा डेटा ॲट्रिब्यूट लागू करा आणि तुमच्या टोकन डेफिनिशन्सना त्याच्या अंतर्गत स्कोप करा.
लाईट मोडसाठी तुमचे डिफॉल्ट्स :root वर सेट करा जेणेकरून JavaScript चालण्यापूर्वी पेज व्यवस्थित रेंडर होईल. त्यानंतर [data-theme="dark"] अंतर्गत टोकन व्हॅल्यूज ओव्हरराइड करा. एक छोटा स्क्रिप्ट टॉगल क्लिकवर लक्ष ठेवतो, ॲट्रिब्यूट अपडेट करतो आणि पेजवरील प्रत्येक कंपोनंट त्वरित प्रतिसाद देतो. वैयक्तिक एलिमेंट्सवर क्लास थ्रॅशिंगची (class thrashing) गरज पडत नाही. रेंडरिंग दरम्यान पूर्णपणे वेगळी स्टाईलशीट इम्पोर्ट करण्याची गरज पडत नाही. ब्राउझरकडे व्हेरिएबल्स आधीच मेमरीमध्ये असतात; तो फक्त नवीन व्हॅल्यूजसह रिपेंट (repaint) करतो.
यामुळे तुमचा कोड अत्यंत व्यावहारिक दृष्टीने स्वच्छ राहतो. .card च्या प्रत्येक इन्स्टन्सचा शोध घेण्यासाठी तुम्हाला दोन वेगवेगळ्या डिरेक्टरीजमध्ये 'grep' करण्याची गरज पडत नाही. एकाच नोडवर असलेल्या स्पर्धात्मक थीम क्लासेसमधील 'स्पेसिफिसिटी वॉर' (specificity wars) बद्दल तुम्हाला काळजी करण्याची गरज नाही. तुमचे HTML वाचनीय राहते. तुमचे CSS सेंट्रलाइज्ड आणि शोधण्यायोग्य राहते.
हे व्हॅल्यूजबद्दल आहे, व्हर्जनबद्दल नाही
डार्क मोड हा व्हॅल्यूजबद्दल आहे. तो तुमच्या UI चा दुसरा व्हर्जन नाही. रात्री तुमच्या कार्डचे कोपरे अधिक गोलाकार होत नाहीत. तुमचे ग्रिड वेगळ्या आकारात कोसळत नाही. तुमच्या टाईप स्केलला नवीन रिदमची गरज नसते. फक्त रंग बदलतात आणि कधीकधी शॅडो (shadows) थोड्या गडद होतात. डार्क मोडला पूर्ण 'रीस्किन' (reskin) म्हणून ट्रीट करणे हे ओव्हर-इंजिनिअरिंग आहे, ज्यामुळे मेंटेनन्सचे nightmare (दुष्ट स्वप्न) निर्माण होतात.
ज्या टीम्स हे नीट समजून घेतात, त्या त्यांच्या डिझाइन सिस्टमला डेटाबेसप्रमाणे वागवतात. कंपोनंट्स नावाप्रमाणे प्रॉपर्टीजसाठी क्वेरी करतात. थीम्स रेकॉर्ड्स पुरवतात. लाईट ते डार्कमध्ये स्विच करणे म्हणजे केवळ 'क्वेरी पॅरामीटर' बदलणे आहे, 'स्कीमा रीराईट' (schema rewrite) नाही.
हीच मानसिकता तुम्हाला 'थीम ड्रिफ्ट'पासून वाचवते. एक कार्ड. एक बटण. स्पेसिंग आणि साइजिंगसाठी एकच 'सोर्स ऑफ ट्रुथ' (source of truth). पॅलेट एकाच ठिकाणी असते, लॉजिकली मॅप्ड असते आणि वापरकर्त्याला हव्या असलेल्या कोणत्याही वातावरणासाठी तयार असते.
मुख्य निष्कर्ष (The Real Takeaway)
जर तुम्ही लाईट आणि डार्क मोडसाठी दोन CSS फाइल्स मेंटेन करत असाल, तर तुम्ही थीमिंग करत नाही आहात, तर तुम्ही क्लोनिंग करत आहात. सिमेंटिक टोकन्सकडे (semantic tokens) वळा, त्यांना रूट-लेव्हल डेटा ॲट्रिब्यूटसह स्कोप करा आणि तुमच्या कंपोनंट्सना अपिअरन्स हार्डकोड करण्याऐवजी 'रोल्स' (roles) मागू द्या. सुरुवातीला रिफॅक्टरिंगसाठी कष्ट घ्यावे लागतील, पण पर्याय म्हणजे समांतर स्टाईलशीट्समध्ये 'व्हॅक-अ-मोले' (whack-a-mole) सारखा कधीही न संपणारा खेळ खेळणे. एकाच कार्डला दोनदा लिहिण्यासाठी आयुष्य खूप लहान आहे.
