ڈویلپرز کو فوری کامیابی پسند ہوتی ہے۔ جب ٹکٹ پر "ڈارک موڈ شامل کریں" لکھا ہو، تو سب سے آسان راستہ واضح نظر آتا ہے: light.css لکھیں، dark.css لکھیں، اور ان کے درمیان سوئچ کریں۔ یہ صاف ستھرا لگتا ہے۔ یہ تیزی سے مکمل ہو جاتا ہے۔ تین اجزاء (components) والے ایک چھوٹے سے سائیڈ پروجیکٹ کے لیے یہ شاید ٹھیک رہے۔ لیکن جیسے ہی آپ کی ایپلی کیشن چند ماڈیولز سے آگے بڑھتی ہے، وہ دوسری فائل اثاثہ رہنے کے بجائے ایک بوجھ بن جاتی ہے جسے آپ کو دو بار برقرار رکھنا پڑتا ہے۔
دو فائلوں کا جال
پہلی نظر میں یہ منطق درست معلوم ہوتی ہے۔ ذمہ داریوں کی تقسیم (Separation of concerns)، ہے نا؟ ہلکی چیزیں یہاں، گہری چیزیں وہاں۔ آپ اپنے ایڈیٹر میں دو بفرز کھولتے ہیں۔ آپ لائٹ فائل سے کارڈ کے اسٹائلز کاپی کر کے ڈارک فائل میں ڈالتے ہیں، #ffffff کو #1a1a1a سے بدلتے ہیں، اور کام ختم کر دیتے ہیں۔
مسئلہ پہلے ہفتے میں نہیں ہوتا۔ مسئلہ چھٹے مہینے میں ہوتا ہے، جب ایک ڈیزائنر پرائمری بٹن پر تھوڑا مختلف border radius مانگتا ہے، یا جب پروڈکٹ ٹیم چیک آؤٹ فارم پر ایک نیا وارننگ اسٹیٹ (warning state) چاہتی ہے۔ آپ لائٹ اسٹائل شیٹ کو اپ ڈیٹ کرتے ہیں۔ آپ ڈارک اسٹائل شیٹ پر نظر ڈالتے ہیں۔ شاید آپ تبدیلی کو کاپی کرنا یاد رکھیں۔ شاید آپ نہ رکھیں۔ وہ فرق ہی وہ جگہ ہے جہاں معیار ختم ہو جاتا ہے۔ اب آپ ایک انٹرفیس کو برقرار نہیں رکھ رہے ہوتے۔ آپ دو متوازی انٹرفیس برقرار رکھ رہے ہوتے ہیں جو محض ایک ہی HTML ڈھانچے کو استعمال کر رہے ہوتے ہیں۔
تھیم ڈرفٹ (Theme Drift) ناگزیر ہے
اس فرق کا ایک نام ہے جسے فرنٹ اینڈ ٹیمیں اب پہچاننے لگی ہیں: تھیم ڈرفٹ (theme drift)۔ یہ اس وقت ہوتا ہے جب آپ کی دو اسٹائل شیٹس مختلف رفتار سے ترقی کرتی ہیں۔ یہاں پیڈنگ (padding) میں تبدیلی، وہاں شیڈو (shadow) میں تھوڑی سی تبدیلی۔ ڈارک فائل نظر انداز شدہ بہن بن جاتی ہے۔ یا اس سے بھی بدتر، یہ خوف کا باعث بن جاتی ہے۔ ڈویلپرز تبدیلیوں سے بچنے لگتے ہیں کیونکہ ایک تھیم کو چھونے کا مطلب ہے کام کو دوہرا کرنے کے لیے دوسری فائل میں تلاش کرنا۔
ذہنی بوجھ (Cognitive overhead) تیزی سے بڑھتا ہے۔ آپ CSS ایک بار لکھنا چاہتے تھے۔ اس کے بجائے آپ نے اسے دو بار لکھا، اور اب ہر بار ڈیزائن سسٹم بدلنے پر آپ اس قرض پر سود ادا کر رہے ہیں۔ ڈارک موڈ میں آئیکنز اپنی جگہ سے ہٹ جاتے ہیں کیونکہ کسی نے لائٹ فائل میں flex gap اپ ڈیٹ کیا اور اسے ڈارک فائل میں کرنا بھول گیا۔ فوکس رنگز (focus rings) غائب ہو جاتے ہیں کیونکہ ایک نیا رسائی کا اصول (accessibility rule) صرف ایک شیٹ میں شامل کیا گیا تھا۔ UI صرف غلط نہیں لگتا، بلکہ یہ ٹوٹا ہوا محسوس ہونے لگتا ہے۔
سیمنٹک ٹوکنز (Semantic Tokens) کا استعمال کریں
اس کا حل کوئی بہتر 'diff tool' یا سخت کوڈ ریویو نہیں ہے۔ اس کا حل رنگ کے بارے میں سوچنے کا ایک مختلف طریقہ ہے۔ اپنے اسٹائلز کو ظاہری شکل کے بجائے مقصد کے لحاظ سے ترتیب دینا شروع کریں۔ یہیں سے سیمنٹک ٹوکنز (semantic tokens) کا کردار شروع ہوتا ہے۔
کارڈ کو سفید پس منظر دینے کے بجائے، اسے 'surface background' دیں۔ ٹیکسٹ کے لیے کالے یا آف وائٹ کے درمیان انتخاب کرنے کے بجائے، ایک 'text color' منتخب کریں۔ کمپوننٹ کو یہ معلوم نہیں ہوتا یا اس سے کوئی فرق نہیں پڑتا کہ صارف لائٹ موڈ پسند کرتا ہے یا ڈارک۔ وہ صرف اس ٹوکن کا مطالبہ کرتا ہے جو اس کے کام سے مطابقت رکھتا ہو۔
ایک عام بٹن کے بارے میں سوچیں۔ دو فائلوں والی دنیا میں، .btn لائٹ اسٹائل شیٹ میں رہتا ہے جس کا پس منظر سفید اور بارڈر گہرا ہوتا ہے۔ اس کا ہمزاد ڈارک اسٹائل شیٹ میں رہتا ہے جس کا پس منظر تقریباً سیاہ اور بارڈر ہلکا ہوتا ہے۔ یہ ایک بٹن کے لیے دوگنا کوڈ ہے۔ ٹوکنز کے ساتھ، .btn کا صرف ایک اعلان (declaration) ہوتا ہے: پس منظر var(--color-surface-secondary) ہے اور بارڈر var(--color-border-default) ہے۔ اصل ویلیوز (values) روٹ (root) پر ہوتی ہیں۔ جب سائٹ لائٹ موڈ میں ہوتی ہے، تو --color-surface-secondary کا نتیجہ #f8f9fa جیسا کچھ نکلتا ہے۔ ڈارک موڈ میں، وہی ٹوکن #2d2d2d پر حل ہو جاتا ہے۔ بٹن کمپوننٹ کبھی نہیں بدلتا۔ صرف اس کے نیچے کا ڈیٹا بدلتا ہے۔
ڈھانچے (structure) اور ڈیٹا کے درمیان یہ فرق باریک ہے لیکن طاقتور ہے۔ آپ کا کارڈ کمپوننٹ لے آؤٹ، اسپیسنگ، ٹائپوگرافی اور ایلیویشن (elevation) کو ایک بار بیان کرتا ہے۔ آپ کا تھیم لیئر پیلیٹ (palette) کو بیان کرتا ہے۔ یہی وہ علیحدگی ہے جس کے لیے CSS custom properties بنائی گئی تھیں۔
آرکیٹیکچر کیسے بدلتا ہے
یہ طریقہ بنیادی طور پر آپ کے اسٹائل لکھنے کے انداز کو دوبارہ ترتیب دیتا ہے۔
پرانا طریقہ عام طور پر ایسا ہوتا ہے:
- ایک لائٹ کارڈ اسٹائل شیٹ جو پیڈنگ، ریڈیس، بیک گراؤنڈ، ٹیکسٹ کلر اور شیڈو کو بیان کرتی ہے۔
- ایک ڈارک کارڈ اسٹائل شیٹ جو صرف رنگ بدلنے کے لیے زیادہ تر انہی پراپرٹیز کو دوبارہ بیان کرتی ہے۔
- ایک لاجک لیئر جو فیصلہ کرتی ہے کہ کون سی اسٹائل شیٹ لوڈ کرنی ہے یا باڈی (body) پر کون سا کلاس ٹوگل کرنا ہے۔
نیا طریقہ ایسا لگتا ہے:
- ایک کارڈ اسٹائل شیٹ جو لے آؤٹ کو بیان کرتی ہے اور سیمنٹک ٹوکنز تفویض کرتی ہے۔
- ایک تھیم فائل جو یہ بیان کرتی ہے کہ لائٹ سیاق و سباق (context) میں ان ٹوکنز کا کیا مطلب ہے۔
- ایک تھیم فائل، یا اسی فائل میں ایک بلاک، جو یہ بیان کرتی ہے کہ ڈارک سیاق و سباق میں ان ٹوکنز کا کیا مطلب ہے۔
- ایک واحد ایٹریبیوٹ سوئپ (attribute swap) جو کمپوننٹ لیئر کو چھوئے بغیر ویلیو لیئر کو تبدیل کر دیتا ہے۔
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.
