ডেভেলপাররা দ্রুত সাফল্য পছন্দ করেন। যখন টিকিটে লেখা থাকে "ডার্ক মোড যোগ করুন," তখন সবচেয়ে সহজ পথটি খুব স্পষ্ট মনে হয়: light.css লিখুন, dark.css লিখুন, এবং তাদের মধ্যে টগল করুন। এটি পরিষ্কার মনে হয়। এটি দ্রুত ডেলিভারি করা যায়। মাত্র তিনটি কম্পোনেন্ট বিশিষ্ট একটি ছোট সাইড প্রজেক্টের জন্য এটি কাজ করতে পারে। কিন্তু আপনার অ্যাপ্লিকেশনটি যখন কয়েকটি মডিউলের বাইরে বড় হতে শুরু করে, তখন সেই দ্বিতীয় ফাইলটি আর সম্পদ থাকে না, বরং এটি একটি দ্বিগুণ রক্ষণাবেক্ষণের বোঝা হয়ে দাঁড়ায়।

দুই-ফাইলের ফাঁদ

প্রথম দেখায় যুক্তিটি সঠিক মনে হয়। কনসার্ন বা দায়িত্বের বিভাজন, তাই না? হালকা বিষয় এখানে, অন্ধকার বিষয় ওখানে। আপনি আপনার এডিটরে দুটি বাফার খুলেন। আপনি লাইট ফাইল থেকে কার্ডের স্টাইলগুলো ডার্ক ফাইলে কপি করেন, #ffffff-এর পরিবর্তে #1a1a1a বসান এবং কাজ শেষ করেন।

সমস্যাটি প্রথম সপ্তাহে হয় না। সমস্যাটি হয় ছয় মাস পর, যখন একজন ডিজাইনার প্রাইমারি বাটনের বর্ডার রেডিয়াস কিছুটা পরিবর্তন করতে বলেন, অথবা যখন প্রোডাক্ট টিম চেকআউট ফর্মে একটি নতুন ওয়ার্নিং স্টেট চায়। আপনি লাইট স্টাইলশিটটি আপডেট করেন। আপনি ডার্ক স্টাইলশিটটি চোখ বুলিয়ে দেখেন। হয়তো আপনি পরিবর্তনটি কপি করতে মনে রাখেন। হয়তো ভুলে যান। এই ব্যবধানেই কোয়ালিটি নষ্ট হয়ে যায়। আপনি আর একটি ইন্টারফেস রক্ষণাবেক্ষণ করছেন না। আপনি দুটি সমান্তরাল ইন্টারফেস রক্ষণাবেক্ষণ করছেন যা একই HTML স্কেলেটন শেয়ার করে।

থিম ড্রিফট অনিবার্য

এই ব্যবধানের একটি নাম আছে যা ফ্রন্টএন্ড টিমগুলো চিনতে শুরু করেছে: থিম ড্রিফট (theme drift)। এটি তখন ঘটে যখন আপনার দুটি স্টাইলশিট ভিন্ন ভিন্ন গতিতে বিবর্তিত হয়। এখানে একটি প্যাডিং অ্যাডজাস্টমেন্ট, সেখানে একটি শ্যাডো পরিবর্তন। ডার্ক ফাইলটি অবহেলিত হয়ে পড়ে। অথবা আরও খারাপভাবে, এটি ভয়ের কারণ হয়ে দাঁড়ায়। ডেভেলপাররা পরিবর্তন এড়াতে শুরু করেন কারণ একটি থিমে হাত দেওয়া মানে অন্য একটি ফাইলে গিয়ে একই কাজ ডুপ্লিকেট করা।

কগনিটিভ ওভারহেড দ্রুত বাড়তে থাকে। আপনি চেয়েছিলেন একবার CSS লিখতে। পরিবর্তে আপনি এটি দুবার লিখেছেন, এবং এখন ডিজাইন সিস্টেম পরিবর্তনের প্রতিটি ক্ষেত্রে আপনি সেই ঋণের সুদ দিচ্ছেন। ডার্ক মোডে আইকনগুলো ভুলভাবে সারিবদ্ধ হয় কারণ কেউ লাইট ফাইলে একটি flex gap আপডেট করেছেন এবং সেটি ডার্ক ফাইলে করতে ভুলে গেছেন। ফোকাস রিং অদৃশ্য হয়ে যায় কারণ একটি নতুন অ্যাক্সেসিবিলিটি রুল শুধুমাত্র একটি শিটে যুক্ত হয়েছে। UI শুধু দেখতেই ভুল লাগে না, এটি ভাঙা বা ত্রুটিপূর্ণ মনে হতে শুরু করে।

সিম্যান্টিক টোকেন ব্যবহার করুন

সমাধানটি কোনো উন্নত ডিফ (diff) টুল বা কঠোর কোড রিভিউ নয়। সমাধানটি হলো রঙের বিষয়ে চিন্তা করার একটি ভিন্ন পদ্ধতি। আপনার স্টাইলগুলোকে বাহ্যিক রূপ অনুযায়ী সাজানো বন্ধ করুন এবং উদ্দেশ্য অনুযায়ী সাজানো শুরু করুন। এখানেই সিম্যান্টিক টোকেন (semantic tokens) কাজে আসে।

একটি কার্ডকে সাদা ব্যাকগ্রাউন্ড দেওয়ার পরিবর্তে, এটিকে একটি 'সারফেস ব্যাকগ্রাউন্ড' (surface background) দিন। টেক্সটের জন্য কালো বা অফ-হোয়াইটের মধ্যে বেছে নেওয়ার পরিবর্তে, একটি 'টেক্সট কালার' (text color) বেছে নিন। কম্পোনেন্টটি জানে না বা গ্রাহক লাইট নাকি ডার্ক মোড পছন্দ করেন। এটি কেবল সেই টোকেনটি চায় যা তার কাজের সাথে মিলে যায়।

একটি স্ট্যান্ডার্ড বাটনের কথা ভাবুন। দুই-ফাইলের জগতে, .btn লাইট স্টাইলশিটে থাকে যার ব্যাকগ্রাউন্ড সাদা এবং বর্ডার গাঢ়। এর যম বা টুইন ডার্ক স্টাইলশিটে থাকে যার ব্যাকগ্রাউন্ড প্রায় কালো এবং বর্ডার কিছুটা হালকা। একটি বাটনের জন্য এটি দ্বিগুণ কোড। টোকেন ব্যবহার করলে, .btn-এর একটি মাত্র ডিক্লারেশন থাকে: ব্যাকগ্রাউন্ড হলো var(--color-surface-secondary) এবং বর্ডার হলো var(--color-border-default)। মানগুলো (values) রুট লেভেলে থাকে। যখন সাইটটি লাইট মোডে থাকে, --color-surface-secondary এর মান #f8f9fa-এর মতো কিছু হয়। ডার্ক মোডে, একই টোকেন #2d2d2d হিসেবে কাজ করে। বাটন কম্পোনেন্টটি কখনোই পরিবর্তন হয় না। শুধুমাত্র এর নিচের ডেটা পরিবর্তিত হয়।

গঠন (structure) এবং ডেটার (data) মধ্যে এই পার্থক্যটি সূক্ষ্ম কিন্তু শক্তিশালী। আপনার কার্ড কম্পোনেন্ট লেআউট, স্পেসিং, টাইপোগ্রাফি এবং এলিভেশন একবারই সংজ্ঞায়িত করে। আপনার থিম লেয়ার প্যালেট সংজ্ঞায়িত করে। এই বিভাজনটি ঠিক সেই কারণেই তৈরি করা হয়েছে যার জন্য CSS কাস্টম প্রপার্টিজ (custom properties) ব্যবহৃত হয়।

আর্কিটেকচার কীভাবে পরিবর্তিত হয়

এই পদ্ধতিটি আপনি কীভাবে স্টাইল লিখবেন তা মৌলিকভাবে পুনর্গঠিত করে।

পুরনো পদ্ধতিটি সাধারণত এমন দেখায়:

  • একটি লাইট কার্ড স্টাইলশিট যা প্যাডিং, রেডিয়াস, ব্যাকগ্রাউন্ড, টেক্সট কালার এবং শ্যাডো সংজ্ঞায়িত করে।
  • একটি ডার্ক কার্ড স্টাইলশিট যা শুধুমাত্র রঙ পরিবর্তন করার জন্য একই প্রপার্টিগুলোর বেশিরভাগই পুনরায় সংজ্ঞায়িত করে।
  • একটি লজিক লেয়ার যা সিদ্ধান্ত নেয় কোন স্টাইলশিট লোড করতে হবে বা বডিতে কোন ক্লাস টগল করতে হবে।

নতুন পদ্ধতিটি এমন দেখায়:

  • একটি কার্ড স্টাইলশিট যা লেআউট সংজ্ঞায়িত করে এবং সিম্যান্টিক টোকেন অ্যাসাইন করে।
  • একটি থিম ফাইল যা লাইট কনটেক্সটে সেই টোকেনগুলোর অর্থ কী তা সংজ্ঞায়িত করে।
  • একটি থিম ফাইল, অথবা একই ফাইলের একটি ব্লক, যা ডার্ক কনটেক্সটে সেই টোকেনগুলোর অর্থ কী তা সংজ্ঞায়িত করে।
  • একটি মাত্র অ্যাট্রিবিউট সোয়াপ (attribute swap) যা কম্পোনেন্ট লেয়ারে হাত না দিয়েই ভ্যালু লেয়ার পরিবর্তন করে দেয়।

আপনি সেটআপটিকে স্থিতিশীল রাখেন। আপনি শুধু ডেটা পরিবর্তন করেন। যখন ডিজাইনার একটি তৃতীয় থিম আনতে চান, হতে পারে একটি হাই-কন্ট্রাস্ট মোড বা একটি মিডনাইট ব্লু ভেরিয়েন্ট, তখন আপনাকে কার্ডটি নতুন করে লিখতে হয় না। আপনি শুধু টোকেন ম্যাপে আরও একটি অ্যাসাইনমেন্ট যোগ করেন। কম্পোনেন্টটি নির্বিকার এবং নিশ্চিন্ত থাকে। এটি তখনও একটি সারফেস কালার চায়। থিমটি তাকে বলে দেয় কোন সারফেস কালার ব্যবহার করতে হবে।

ডেটা অ্যাট্রিবিউট সুইচ

ইমপ্লিমেন্টেশন সহজ এবং পঠনযোগ্য রাখা সম্ভব। আপনার HTML ট্যাগে একটি ডেটা অ্যাট্রিবিউট প্রয়োগ করুন, যেমন data-theme="dark", এবং আপনার টোকেন ডেফিনিশনগুলোকে এর অধীনে স্কোপ (scope) করতে দিন।

লাইট এক্সপেরিয়েন্সের জন্য :root-এ আপনার ডিফল্ট মানগুলো সেট করুন যাতে জাভাস্ক্রিপ্ট চলার আগেই পেজটি সঠিকভাবে রেন্ডার হয়। তারপর [data-theme="dark"]-এর অধীনে টোকেন ভ্যালুগুলোকে ওভাররাইড করুন। একটি ছোট স্ক্রিপ্ট টগল ক্লিকের জন্য অপেক্ষা করে, অ্যাট্রিবিউটটি আপডেট করে, এবং পেজের প্রতিটি কম্পোনেন্ট তাৎক্ষণিকভাবে সাড়া দেয়। প্রতিটি এলিমেন্টে আলাদা আলাদা ক্লাস পরিবর্তন করার ঝামেলা নেই। রেন্ডারিং চলাকালীন সম্পূর্ণ আলাদা একটি স্টাইলশিট ইমপোর্ট করার প্রয়োজন নেই। ব্রাউজারের মেমরিতে ইতিমধ্যেই ভেরিয়েবলগুলো থাকে; এটি শুধু নতুন মান দিয়ে রি-পেইন্ট (repaint) করে।

এটি আপনার কোডকে অত্যন্ত ব্যবহারিক উপায়ে পরিষ্কার রাখে। .card-এর প্রতিটি উদাহরণ খুঁজে পেতে আপনাকে দুটি ডিরেক্টরি জুড়ে grep করতে হবে না। একই নোডে স্তূপীকৃত প্রতিদ্বন্দ্বী থিম ক্লাসগুলোর মধ্যে স্পেসিফিসিটি যুদ্ধ (specificity wars) নিয়ে আপনাকে চিন্তিত হতে হবে না। আপনার HTML পঠনযোগ্য থাকে। আপনার CSS সেন্ট্রালাইজড এবং সার্চযোগ্য থাকে।

এটি ভ্যালু নিয়ে, ভার্সন নিয়ে নয়

ডার্ক মোড হলো ভ্যালু বা মানের ব্যাপার। এটি আপনার UI-এর দ্বিতীয় কোনো ভার্সন নয়। রাতে আপনার কার্ডের কোনাগুলো আরও গোল হয়ে যায় না। আপনার গ্রিড অন্য কোনো আকৃতিতে ভেঙে পড়ে না। আপনার টাইপ স্কেল (type scale)-এর নতুন কোনো ছন্দের প্রয়োজন হয় না। শুধু রঙগুলো পরিবর্তিত হয়, এবং মাঝে মাঝে শ্যাডো বা ছায়াগুলো কিছুটা গভীর হয়। ডার্ক মোডকে সম্পূর্ণ নতুন একটি রিস্কিন (reskin) হিসেবে দেখা হলো ওভার-ইঞ্জিনিয়ারিং, যা রক্ষণাবেক্ষণের দুঃস্বপ্ন তৈরি করে।

যে দলগুলো এটি সঠিকভাবে বোঝে, তারা তাদের ডিজাইন সিস্টেমকে একটি ডেটাবেসের মতো বিবেচনা করে। কম্পোনেন্টগুলো নামের মাধ্যমে প্রপার্টিজগুলো কুয়েরি (query) করে। থিমগুলো রেকর্ড সরবরাহ করে। লাইট থেকে ডার্ক মোডে পরিবর্তন করা হলো একটি কুয়েরি প্যারামিটার পরিবর্তন, কোনো স্কিমা রিরাইট (schema rewrite) নয়।

এই মানসিকতা আপনাকে থিম ড্রিফট (theme drift) থেকে রক্ষা করে। একটি কার্ড। একটি বাটন। স্পেসিং এবং সাইজিংয়ের জন্য একটি মাত্র সত্য উৎস (source of truth)। প্যালেটটি একটি মাত্র স্থানে থাকে, যা লজিক্যালি ম্যাপ করা এবং ব্যবহারকারী যে কোনো পরিবেশ পছন্দ করুন না কেন, তার জন্য প্রস্তুত।

আসল শিক্ষা

আপনি যদি লাইট এবং ডার্ক মোডের জন্য দুটি আলাদা CSS ফাইল রক্ষণাবেক্ষণ করেন, তবে আপনি থিমিং করছেন না; আপনি ক্লোনিং করছেন। সিম্যান্টিক টোকেনে (semantic tokens) চলে আসুন, সেগুলোকে রুট-লেভেল ডেটা অ্যাট্রিবিউট দিয়ে স্কোপ করুন, এবং আপনার কম্পোনেন্টগুলোকে অ্যাপিয়ারেন্স হার্ডকোড করার পরিবর্তে রোল (role) চাওয়া শিখিয়ে দিন। প্রাথমিক রিফ্যাক্টরে কিছুটা পরিশ্রম হলেও, এর বিকল্প হলো সমান্তরাল স্টাইলশিটগুলোর মধ্যে অন্তহীন whack-a-mole খেলার মতো একটি পরিস্থিতি। একই কার্ড দুবার লেখার জন্য জীবন অনেক ছোট।