সরাসরি পর্বে যান
পর্ব ৩৬ · ৫৯-এর মধ্যে · মডিউল ৫

Dark mode আর থিম — prefers-color-scheme আর color-scheme

Dark Mode & Theming — color-scheme, prefers-color-scheme, Semantic Tokens, light-dark() & a Theme Toggle

Taught in Bangla, this lesson covers dark mode and theming: why some people want dark mode, declaring color-scheme, detecting prefers-color-scheme, structuring themes with semantic tokens, the light-dark() function, a theme toggle with data-theme that remembers the choice and avoids a flash, designing dark palettes, handling images, logos and forced colors, and testing themes.

এই পর্বে যা শিখবেন

  • মানুষ কেন dark mode বেছে নেন, কোন দাবি সত্য আর কোনটি অতিরঞ্জন, আর কেন পছন্দের সুযোগ রাখতে হয়
  • color-scheme property ও meta, prefers-color-scheme media query আর light-dark() ফাংশন — কোনটি কী করে, ব্রাউজার কীভাবে ভাবে
  • semantic token দিয়ে এক জায়গা থেকে লাইট, ডার্ক, এমনকি পহেলা বৈশাখ বা ঈদের মতো মৌসুমি থিম চালানো
  • data-theme আর ছোট JavaScript দিয়ে থিম বদলের বোতাম, পছন্দ মনে রাখা আর ভুল থিমের ঝলক এড়ানো
  • ডার্ক প্যালেটের নকশা, দুই থিমে contrast যাচাই, ছবি-লোগো, forced colors আর DevTools-এ পরীক্ষা

১ · মানুষ কেন dark mode চান — আর কেন সবাই চান না

রাত এগারোটা। লোডশেডিংয়ে ঘর অন্ধকার, আপনি বিছানায় শুয়ে ফোনে একটি খবরের সাইট খুললেন। হঠাৎ পুরো পর্দা ধবধবে সাদা — চোখ কুঁচকে যায়। অথচ ফোনের সেটিংসে আপনি অনেক আগেই “Dark” বেছে রেখেছেন, আর Facebook, YouTube, bKash অ্যাপ সবই অন্ধকার থিমে খোলে। সমস্যাটা ফোনের নয় — সাইটটি আপনার পছন্দ শোনেনি।

Dark mode মানে গাঢ় পটভূমিতে হালকা লেখা। এর উল্টোটা লাইট মোড — সাদা বা হালকা পটভূমিতে গাঢ় লেখা, ঠিক কাগজের বইয়ের মতো। আজ প্রায় সব অপারেটিং সিস্টেম (Windows, macOS, Android, iOS) ব্যবহারকারীকে এ দুটির একটি বেছে নিতে দেয়, আর ব্রাউজার সেই পছন্দ ওয়েবপেজকেও জানিয়ে দেয়। এই পর্বে দেখব, পাতা কীভাবে সেই পছন্দ শুনবে।

প্রথমে বুঝে নিই মানুষ কেন dark mode চান। কারণগুলো সত্যি, কিন্তু প্রায়ই বাড়িয়ে বলা হয়:

অন্ধকারে আরাম

চারপাশ অন্ধকার থাকলে উজ্জ্বল সাদা পর্দা চোখে ধাক্কা দেয়। গাঢ় পর্দা কম আলো ছড়ায়, তাই রাতে অনেকের কাছে আরামদায়ক লাগে। তবে “dark mode চোখের ক্ষতি সারায়” — এমন কোনো প্রমাণ নেই।

মূল কারণ: পছন্দ আর আরাম

ব্যাটারি — শর্তসহ

OLED পর্দায় প্রতিটি পিক্সেল নিজেই আলো দেয়, তাই গাঢ় পিক্সেলে কম বিদ্যুৎ লাগে। বেশি উজ্জ্বলতায় সাশ্রয় চোখে পড়ার মতো, কম উজ্জ্বলতায় সামান্য। আর LCD পর্দায় পেছনের আলো সবসময় জ্বলে, সেখানে প্রায় কোনো লাভ নেই।

পর্দার ধরন ও উজ্জ্বলতার ওপর নির্ভর

সবার চোখ এক নয়

আলোয় অতি সংবেদনশীল কিছু মানুষ, মাইগ্রেনের রোগী বা চোখের কিছু সমস্যায় গাঢ় পর্দা সাহায্য করে। আবার astigmatism (চোখের লেন্সের বাঁকজনিত সমস্যা) থাকলে অনেকের কাছে কালোর ওপর সাদা লেখা ঝাপসা, ছড়ানো দেখায়। দিনের আলোয় বেশিরভাগ মানুষ হালকা পটভূমিতেই দ্রুত পড়েন।

তাই দুটিই দিন
মূল ধারণা

Dark mode কোনো “ভালো” বা “আধুনিক” ডিজাইন নয় — এটি একটি accessibility আর আরামের পছন্দ। ভালো ওয়েবসাইট তিনটি কাজ করে: (১) ব্যবহারকারীর সিস্টেমের পছন্দ নিজে থেকেই মানে, (২) পাতার ভেতরে বদলানোর সুযোগ দেয়, (৩) দুই থিমেই সমান পড়ার যোগ্য থাকে।

অন্ধকার পর্দা আসলে পুরোনো জিনিস। ১৯৭০–৮০-এর দশকের কম্পিউটার টার্মিনালে কালো পর্দায় সবুজ বা কমলা অক্ষর জ্বলত — প্রযুক্তিটাই এমন ছিল। পরে গ্রাফিক্যাল ইন্টারফেস এলো “কাগজের পাতা”র রূপক নিয়ে, তাই সাদা পটভূমি হলো স্বাভাবিক। ২০১৮ সালে Apple macOS Mojave-এ সিস্টেম-জোড়া dark mode আনে, ২০১৯ সালে iOS 13 আর Android 10-ও আনে। প্রায় একই সময়ে, ২০১৯ সালে, প্রধান ব্রাউজারগুলো prefers-color-scheme media query চালু করে — ওয়েবপেজ প্রথমবারের মতো জানতে পারল ব্যবহারকারী কোন থিম চান।

২ · color-scheme — ব্রাউজারকে বলুন “আমি দুই থিমই পারি”

অনেকে dark mode বানাতে গিয়ে শুধু body-র পটভূমি কালো আর লেখা সাদা করে দেন। তারপর দেখেন — ফর্মের input বাক্স তখনো সাদা, ড্রপডাউন সাদা, scrollbar সাদা, আর পাতা লোড হওয়ার মুহূর্তে এক ঝলক সাদা। কারণ এসব অংশের রং আপনার CSS ঠিক করে না, ঠিক করে ব্রাউজারের নিজস্ব user agent stylesheet আর অপারেটিং সিস্টেম।

এখানেই color-scheme property-র কাজ। এটি কোনো রং বসায় না; বরং ব্রাউজারকে জানায় পাতাটি কোন কোন থিমে ঠিকঠাক দেখায়। তখন ব্রাউজার নিজের আঁকা অংশগুলো — পাতার ডিফল্ট পটভূমি, ডিফল্ট লেখার রং, form control, scrollbar — সেই থিমে আঁকে। নিচের এডিটরে CSS-এ মাত্র একটি লাইন আছে:

:root {
  color-scheme: dark;
}

একটিও রং না লিখে পুরো পাতা অন্ধকার: পটভূমি গাঢ়, লেখা হালকা, আর input, select, checkbox, textarea, বোতাম — সবই ব্রাউজার নিজে গাঢ় রূপে আঁকল; textarea-র scrollbar-ও গাঢ়। চেষ্টা করুন: (১) dark-কে light করুন — সব আবার চেনা সাদা রূপে ফিরল। (২) এবার light dark লিখুন — এখন প্রিভিউ আপনার নিজের ফোন বা কম্পিউটারের থিম মানবে; আপনার সিস্টেম লাইট হলে সাদা, ডার্ক হলে গাঢ়। (৩) :root-এ background: #fff7ed; যোগ করে দেখুন — নিজের দেওয়া রং সবসময় জেতে, color-scheme শুধু ডিফল্টগুলো বদলায়।

লিংকের রং খেয়াল করেছেন? ডার্ক অবস্থায় ব্রাউজার লিংকের ডিফল্ট নীলও হালকা করে দেয়, যাতে গাঢ় পটভূমিতে পড়া যায়। ভেতরে কী ঘটছে, ধাপে ধাপে দেখুন:

  1. আপনার ঘোষণাlight darkপাতা কী কী পারে
  2. ব্যবহারকারীর পছন্দOS: Darkসিস্টেমের সেটিং
  3. ব্রাউজারের সিদ্ধান্তused: darkদুটো মিলিয়ে
  4. ডিফল্ট রং বদলCanvas, CanvasTextফর্ম, scrollbar, পটভূমি
  1. আপনার ঘোষণা। color-scheme: light dark মানে “আমার পাতা দুই থিমেই ঠিক আছে, আমার প্রথম পছন্দ light.” শুধু dark লিখলে মানে “আমি শুধু ডার্ক।” কিছু না লিখলে ব্রাউজার ধরে নেয় পাতাটি শুধু লাইট — সেজন্যই পুরোনো সব সাইট সাদা।
  2. ব্যবহারকারীর পছন্দ। ব্রাউজার অপারেটিং সিস্টেম থেকে জানে ব্যবহারকারী কোন থিম বেছে রেখেছেন (কিছু ব্রাউজারে নিজস্ব থিম-সেটিংও আছে)। এটিই পরে prefers-color-scheme media query হয়ে CSS-এর কাছে আসে।
  3. দুটো মিলিয়ে সিদ্ধান্ত। পাতা যদি ব্যবহারকারীর পছন্দের থিম সমর্থন করে, সেটিই বেছে নেওয়া হয়; না করলে পাতার প্রথম পছন্দ। এই ফলাফলকে স্পেসিফিকেশনে বলে used color scheme. প্রতিটি element-এর জন্য এটি আলাদা হতে পারে, কারণ color-scheme একটি inherited property — একটি অংশে আলাদা মান দিলে শুধু সেখানেই বদলায়।
  4. ডিফল্ট রং বদল। ব্রাউজারের নিজের রংগুলো আসলে নাম দেওয়া system color — Canvas (পটভূমি), CanvasText (লেখা), LinkText, ButtonFace, Field ইত্যাদি। used color scheme ডার্ক হলে এদের মান গাঢ় সংস্করণে বদলে যায়। আপনার নিজের লেখা রং (যেমন background: #fff7ed) এতে বদলায় না — সেগুলো বদলাতে হবে আপনাকেই, পরের অংশগুলোতে যা দেখব।

meta ট্যাগ — CSS আসার আগেই খবর দেওয়া

একই কাজ HTML-এ <meta> দিয়েও করা যায়। পার্থক্য সময়ে: <head>-এর একেবারে শুরুতে থাকা meta ব্রাউজার CSS ফাইল নামার আগেই পড়ে ফেলে। ফলে ধীর ইন্টারনেটে CSS আসতে দেরি হলেও প্রথম পর্দাটি গাঢ় হয়, সাদা ঝলক দেখা যায় না। পেশাদার পথ হলো দুটোই রাখা:

<head>
  <meta charset="utf-8">
  <meta name="color-scheme" content="light dark">
  <link rel="stylesheet" href="style.css">
</head>

/* style.css */
:root { color-scheme: light dark; }

color-scheme এখন সব প্রধান ব্রাউজারে আছে — Baseline Widely available. পুরোনো ব্রাউজার এটি না চিনলে শুধু উপেক্ষা করে, পাতা আগের মতো লাইট থাকে — কোনো ক্ষতি নেই।

আরও গভীরে: color-scheme আর system color-এর নিয়ম লেখা আছে “CSS Color Adjustment” স্পেসিফিকেশনে। একটি সূক্ষ্ম কথা: ভেতরের iframe-এর used color scheme যদি বাইরের পাতার থেকে আলাদা হয়, ব্রাউজার iframe-এর পটভূমি স্বচ্ছ না রেখে তার নিজের থিমের অস্বচ্ছ Canvas রঙে আঁকে — নইলে সাদা পাতার ভেতরে হালকা লেখা পড়াই যেত না। আরেকটি কম জানা মান only: কিছু ব্রাউজার (যেমন Android-এর Chrome-এর একটি সেটিং) dark mode না-জানা পাতাকে নিজে থেকে হিসাব কষে অন্ধকার করে দেয়; color-scheme: only light লিখলে পাতাটি সেই স্বয়ংক্রিয় অন্ধকার থেকে বাদ পড়ার অনুরোধ জানায়। ব্রাউজারভেদে এর মান্যতা আলাদা, তাই এর ওপর অন্ধভাবে ভরসা না করে নিজে ঠিকঠাক ডার্ক থিম দেওয়াই নিরাপদ।

৩ · prefers-color-scheme — ব্যবহারকারীর পছন্দ জিজ্ঞেস করা

color-scheme শুধু ব্রাউজারের ডিফল্ট রং বদলায়। কিন্তু আপনার ব্র্যান্ডের সবুজ বোতাম, কার্ডের পটভূমি, ব্যাজের রং — এগুলো আপনি নিজে লিখেছেন, তাই ডার্ক থিমে এদের নতুন মান আপনাকেই দিতে হবে। সেই সুযোগ দেয় একটি media query: prefers-color-scheme. এর মান হয় light অথবা dark.

দোকানের সাইনবোর্ডের কথা ভাবুন। দিনে সাদা বোর্ডে কালো অক্ষর সবচেয়ে পরিষ্কার পড়া যায়; রাতে সেই একই দোকান আলো-জ্বলা বোর্ড জ্বালায় — অন্ধকারে উজ্জ্বল অক্ষর। দোকানের নাম, লোগো, ঠিকানা একই; বদলেছে শুধু রং, আর বদলের সিদ্ধান্ত নিয়েছে চারপাশের অবস্থা। prefers-color-scheme হলো পাতার জন্য সেই “এখন দিন না রাত” প্রশ্ন — শুধু উত্তরটা আসে ব্যবহারকারীর সেটিং থেকে, ঘড়ি থেকে নয়।

:root { color-scheme: light dark; }
body  { font-family: sans-serif; background: #f4f4f5; color: #1f2937; }
.card { background: #ffffff; padding: 12px 16px; border-radius: 10px;
        border: 1px solid #d4d4d8; }
.badge { background: #dcfce7; color: #166534;
         padding: 2px 8px; border-radius: 6px; }

@media (prefers-color-scheme: dark) {
  body   { background: #18181b; color: #e4e4e7; }
  .card  { background: #27272a; border-color: #3f3f46; }
  .badge { background: #14532d; color: #bbf7d0; }
}

আপনার সিস্টেম লাইট মোডে থাকলে সাদা কার্ড আর হালকা সবুজ ব্যাজ দেখছেন; ডার্ক মোডে থাকলে গাঢ় কার্ড আর গাঢ় সবুজ ব্যাজে হালকা লেখা। প্রিভিউ আপনার আসল সেটিং মানছে। অন্য রূপটি দেখতে সেটিং বদলানোর দরকার নেই: (১) @media-র ভেতরে dark-কে light করুন — এখন লাইট সিস্টেমে ডার্ক নিয়মগুলো চালু হবে (শুধু পরীক্ষার কৌশল, আসল সাইটে এমন লিখবেন না)। (২) প্রথম লাইনটি মুছে দিন, তারপর ডার্ক রূপে দেখুন — কার্ড ঠিক থাকলেও পাতার scrollbar আর ফর্মের অংশ লাইট থেকে যেত; তাই color-scheme আর media query একসাথে লাগে।

JavaScript থেকেও একই প্রশ্ন করা যায় — matchMedia() দিয়ে। নিচের কোড চালান; ফলাফল আপনার নিজের যন্ত্রের সেটিং অনুযায়ী আসবে। এর সাথে আরও কয়েকটি user preference media feature-ও দেখানো হলো, যেগুলো একই পরিবারের সদস্য:

const dark = matchMedia('(prefers-color-scheme: dark)');
console.log('ডার্ক থিম পছন্দ?', dark.matches);
console.log('কম অ্যানিমেশন পছন্দ?',
  matchMedia('(prefers-reduced-motion: reduce)').matches);
console.log('বেশি contrast চান?',
  matchMedia('(prefers-contrast: more)').matches);
console.log('forced colors চালু?',
  matchMedia('(forced-colors: active)').matches);

// সিস্টেমের থিম বদলালে সাথে সাথে খবর পাওয়া
dark.addEventListener('change', e =>
  console.log('থিম বদলাল →', e.matches ? 'dark' : 'light'));

প্রতিটি লাইনে true বা false আসবে — যেমন লাইট সিস্টেমে প্রথমটি false. শেষ অংশটি একটি শ্রোতা বসায়: পাতা খোলা অবস্থায় সিস্টেমের থিম বদলালে (যেমন ফোনে সন্ধ্যায় স্বয়ংক্রিয় ডার্ক চালু হলে) CSS-এর media query তো নিজে থেকেই আবার মিলিয়ে নেয়, কিন্তু JavaScript-এ আঁকা চার্ট বা ক্যানভাসকে এভাবে খবর দিতে হয়। চেষ্টা করুন: prefers-color-scheme: dark-কে light করে চালান — উত্তর উল্টে যাবে।

দুটি সাধারণ ভুল। (১) prefers-color-scheme: no-preference — পুরোনো খসড়ায় এই মান ছিল, পরে বাদ দেওয়া হয়; এখন শুধু light আর dark. (২) সময় দেখে থিম বদলানো (“সন্ধ্যা ৬টার পর ডার্ক”) — ব্যবহারকারী হয়তো দুপুরেই অন্ধকার ঘরে, বা রাতে আলো-ভরা অফিসে। অনুমান নয়, তাঁর বলা পছন্দ মানুন।

৪ · থিমের কাঠামো — semantic token দিয়ে এক জায়গায় রং

উপরের উদাহরণে একটা সমস্যা লুকিয়ে আছে। প্রতিটি selector-এর রং দুই জায়গায় লেখা — একবার স্বাভাবিক নিয়মে, আরেকবার media query-র ভেতরে। ৩টি selector-এ এটা চলে; বাস্তবের সাইটে শত শত selector থাকলে কোথাও না কোথাও একটি ডার্ক রং লিখতে ভুলে যাবেনই। সমাধান হলো পর্ব ৩৩-এ (CSS variable — একবার লিখে বারবার ব্যবহার) দেখা custom property.

কিন্তু শুধু variable বানালেই হয় না, নাম দেওয়ার কৌশলটাই আসল। --white: #fff নামের variable ডার্ক থিমে #18181b হয়ে গেলে নামটাই মিথ্যা হয়ে যায়। তাই দুই স্তরে ভাবুন:

স্তরউদাহরণনাম কী বলেথিমে বদলায়?
Raw / primitive (কাঁচা রং)--green-700: #15803dরংটি কেমননা — সবুজ সবসময় সবুজ
Semantic (অর্থবহ)--accent: var(--green-700)রংটি কী কাজেহ্যাঁ — থিম এর মান বদলায়

কম্পোনেন্ট (কার্ড, বোতাম, ব্যাজ) শুধু semantic নাম ব্যবহার করবে — --surface (পটভূমি), --text (লেখা), --accent (ব্র্যান্ডের রং), --border. থিম মানে এই কয়েকটি নামের এক সেট মান। এই নামগুলোকেই ডিজাইনের ভাষায় বলে design token. নিচের এডিটরে একই কার্ড তিনবার আছে, প্রতিটি আলাদা data-theme-এর ভেতরে — তৃতীয়টি পহেলা বৈশাখের মৌসুমি থিম:

/* ১. কাঁচা রং — নাম বলে রংটি কেমন */
:root { --green-700: #15803d; --green-400: #4ade80;
        --zinc-900: #18181b; --zinc-200: #e4e4e7; --red-700: #b91c1c; }

/* ২. semantic token — নাম বলে রংটি কী কাজে */
.theme { --surface: #ffffff; --text: #1f2937;
         --accent: var(--green-700); --border: #d4d4d8; }
.theme[data-theme="dark"] {
  --surface: var(--zinc-900); --text: var(--zinc-200);
  --accent: var(--green-400); --border: #3f3f46; }
.theme[data-theme="boishakh"] {
  --surface: #fff7ed; --text: #431407;
  --accent: var(--red-700); --border: #fdba74; }

/* ৩. কম্পোনেন্ট — শুধু semantic নাম, কোনো সরাসরি রং নেই */
.card { background: var(--surface); color: var(--text);
        border: 2px solid var(--border); border-radius: 10px;
        padding: 12px; margin: 0 0 8px; font-family: sans-serif; }
.card button { background: var(--accent); color: var(--surface);
               border: 0; border-radius: 6px; padding: 6px 14px; margin-top: 8px; }

তিনটি কার্ডের HTML হুবহু এক, .card-এর CSS-ও এক — পার্থক্য শুধু টোকেনের মানে: সাদা কার্ডে সবুজ বোতাম, গাঢ় কার্ডে হালকা সবুজ বোতাম, আর ঘিয়ে রঙের বৈশাখী কার্ডে লাল বোতাম। চেষ্টা করুন: (১) বৈশাখী থিমকে ঈদের থিমে বদলে দিন — শুধু তার চারটি মান: --surface: #ecfdf5; --text: #064e3b; --accent: #047857; --border: #6ee7b7; কম্পোনেন্টের একটি লাইনও ছুঁতে হলো না। (২) ডার্ক থিমে --accent-কে var(--green-700) করুন — গাঢ় সবুজ বোতাম গাঢ় পটভূমিতে প্রায় মিশে যায়; অংশ ৭-এ এর কারণ দেখব।

এই কাঠামো কেন কাজ করে, তা বুঝতে inheritance আর computed value-এর কথা মনে করুন। নিচের ডায়াগ্রামে দেখুন একটি attribute বদলালে ব্রাউজার কীভাবে শত শত element-এর রং বদলে দেয়:

style.cssনিয়মগুলো আগে থেকেই লেখা
:root               { --surface: #ffffff; }
:root[data-theme="dark"] { --surface: #18181b; }
.card               { background: var(--surface); }

<html>  → --surface = #ffffff
DOMবোতামে ক্লিক → attribute বসল
<html data-theme="dark">

:root[data-theme="dark"] এখন মিলে গেছে
specificity বেশি → --surface = #18181b
Inheritanceমান নিচে নামছে
html     --surface: #18181b
 └ body   --surface: #18181b  (উত্তরাধিকার)
    └ .card  --surface: #18181b
       background: var(--surface) → #18181b
Renderingstyle recalculation → repaint
বদলানো CSS নিয়ম:        ০টি
বদলানো attribute:       ১টি (<html>-এ)
নতুন রঙে আঁকা element:   পাতার সব
  1. নিয়মগুলো আগে থেকেই লেখা। লাইট আর ডার্ক — দুই সেট মানই CSS-এ বসে আছে। এখন <html>-এ কোনো data-theme নেই, তাই শুধু প্রথম নিয়ম মেলে, আর --surface সাদা।
  2. একটি attribute বদল। JavaScript <html>-এ data attribute data-theme="dark" বসাল। এবার দ্বিতীয় selector-ও মেলে, আর তার specificity বেশি (একটি attribute selector বাড়তি), তাই cascade-এ সে জেতে।
  3. মান নিচে নামে। Custom property ডিফল্টভাবে উত্তরাধিকারে যায়। তাই <html>-এর নতুন মান body হয়ে প্রতিটি .card-এ পৌঁছায়। var(--surface)-এর জায়গায় আসল রং বসে computed value হিসাব করার সময় — অর্থাৎ প্রতিটি element নিজের জায়গায় দাঁড়িয়ে “এখন --surface কত?” জিজ্ঞেস করে।
  4. শুধু আবার আঁকা। কোনো CSS নিয়ম লেখা বা মোছা হয়নি, শুধু একটি attribute বদলেছে। ব্রাউজার style আবার হিসাব করে নতুন রঙে আঁকে। এই কারণেই এক ক্লিকে পুরো সাইটের থিম বদলায়, আর থিমের সংখ্যা বাড়ালেও কম্পোনেন্টের CSS এক লাইনও বাড়ে না।

মৌসুমি আর ব্র্যান্ড থিম: পহেলা বৈশাখ, ঈদ, বিজয় দিবস বা একুশে ফেব্রুয়ারির মতো দিনে অনেক দোকান ও সংবাদ সাইট রং বদলায়। Semantic token থাকলে এটি কয়েক লাইনের কাজ — <html data-theme="boishakh">. তবে একটি নিয়ম মনে রাখুন: মৌসুমি থিমও ব্যবহারকারীর ডার্ক পছন্দকে সম্মান করবে। ব্যবহারকারী ডার্ক চাইলে বৈশাখী থিমেরও একটি গাঢ় রূপ দরকার — যেমন [data-theme="boishakh"]-এর ভেতরে আরেকটি @media (prefers-color-scheme: dark), বা পরের অংশের light-dark(). থিম দুই দিকে চলে: “কোন ব্র্যান্ড” আর “লাইট না ডার্ক”।

৫ · light-dark() — একই লাইনে দুই রং

টোকেন থাকলেও এখনো প্রতিটি টোকেন দুই জায়গায় লিখতে হয় — একবার স্বাভাবিক, একবার media query-তে। আধুনিক CSS এটিও সহজ করেছে light-dark() ফাংশন দিয়ে: light-dark(লাইটের-রং, ডার্কের-রং). ব্রাউজার সেই element-এর used color scheme দেখে দুটির একটি বেছে নেয়।

:root {
  color-scheme: light;   /* dark করে দেখুন, তারপর light dark */
  --surface: light-dark(#ffffff, #18181b);
  --text:    light-dark(#1f2937, #e4e4e7);
  --accent:  light-dark(#15803d, #4ade80);
  --soft:    light-dark(#f0fdf4, #052e16);
}
body  { font-family: sans-serif;
        background: var(--surface); color: var(--text); }
.note { background: var(--soft); padding: 8px 12px;
        border-left: 4px solid var(--accent); }

এখন সাদা পাতা, হালকা সবুজ নোটিশ, গাঢ় সবুজ দাগ। চেষ্টা করুন: (১) light-কে dark করুন — একটিও টোকেন না ছুঁয়ে সব রং ডার্ক সংস্করণে গেল, আর input-বোতামও গাঢ় হলো (কারণ একই color-scheme দুটোই চালায়)। (২) color-scheme লাইনটি পুরো মুছে দিন — light-dark() সবসময় প্রথম রং দেখাবে। এটিই সবচেয়ে বড় ফাঁদ: light-dark() নিজে prefers-color-scheme দেখে না, দেখে color-scheme-এর ফল।

এর একটি বড় সুবিধা পরের অংশে কাজে লাগবে: থিম বদলের বোতাম শুধু color-scheme বদলালেই সব light-dark() নিজে থেকে মেনে নেয়। আর color-scheme উত্তরাধিকারে যায়, তাই পাতার শুধু একটি অংশকে (যেমন সবসময়-গাঢ় ভিডিও প্লেয়ার বা footer) color-scheme: dark দিলে সেখানকার সব টোকেন ডার্ক হয়ে যায় — যা media query দিয়ে করা যায় না।

light-dark() ২০২৪ সালে সব প্রধান ব্রাউজারে (Chrome, Edge, Firefox, Safari) আসে — অর্থাৎ Baseline Newly available; Widely available হওয়ার সময়ও প্রায় এসে গেছে। তবু পুরোনো ফোনের ব্রাউজার যাঁরা হালনাগাদ করেন না, তাঁদের জন্য একটি fallback রাখা ভালো।

CSS না-চেনা মান পুরো ঘোষণাসহ উপেক্ষা করে, তাই আগে সাধারণ রং লিখে তার পরে light-dark() লিখুন — নতুন ব্রাউজার দ্বিতীয়টি নেবে, পুরোনোটি প্রথমটি:

:root {
  --surface: #ffffff;                       /* পুরোনো ব্রাউজার: শুধু লাইট */
  --surface: light-dark(#ffffff, #18181b);  /* নতুন ব্রাউজার */
}
@supports not (color: light-dark(#fff, #000)) {
  @media (prefers-color-scheme: dark) { :root { --surface: #18181b; } }
}

বর্তমানে light-dark() শুধু রং নেয়। ছবি বা gradient (background-image) দুই রকম দিতে চাইলে এখনো media query বা টোকেন লাগবে; ছবির জন্য এই ফাংশন বাড়ানোর প্রস্তাব আলোচনায় আছে, কিন্তু Baseline নয়।

৬ · নিজের থিম বোতাম — data-theme, মনে রাখা আর ঝলক এড়ানো

সিস্টেমের পছন্দ মানা প্রথম ধাপ। কিন্তু কেউ হয়তো ফোন রেখেছেন লাইটে, অথচ আপনার লম্বা প্রবন্ধটি রাতে ডার্কে পড়তে চান। তাই ভালো সাইট তিনটি বিকল্প দেয়: সিস্টেম (ডিফল্ট), লাইট, ডার্ক. কৌশলটা সহজ — <html>-এ একটি attribute, আর CSS-এ সেই attribute দেখে color-scheme বদল:

<style>
  :root { color-scheme: light dark;
    --surface: light-dark(#ffffff, #18181b);
    --text: light-dark(#1f2937, #e4e4e7);
    --accent: light-dark(#15803d, #4ade80); }
  :root[data-theme="light"] { color-scheme: light; }
  :root[data-theme="dark"]  { color-scheme: dark; }
  body { background: var(--surface); color: var(--text); font-family: sans-serif; }
  .offer { color: var(--accent); font-weight: bold; }
  [aria-pressed="true"] { outline: 3px solid var(--accent); }
</style>
<p>থিম:
  <button type="button" data-set="system" aria-pressed="true">সিস্টেম</button>
  <button type="button" data-set="light" aria-pressed="false">লাইট</button>
  <button type="button" data-set="dark" aria-pressed="false">ডার্ক</button></p>
<p>আজকের অফার: <span class="offer">মিনিকেট চাল ৳৭২/কেজি</span></p>
<script>
  let choice = 'system';   // আসল সাইটে এটি localStorage-এ থাকবে
  const buttons = document.querySelectorAll('[data-set]');
  buttons.forEach(b => b.addEventListener('click', () => {
    choice = b.dataset.set;
    if (choice === 'system') delete document.documentElement.dataset.theme;
    else document.documentElement.dataset.theme = choice;
    buttons.forEach(x => x.setAttribute('aria-pressed', x === b));
    console.log('থিম → ' + choice);
  }));
</script>

শুরুতে “সিস্টেম” বাছাই করা, তাই পাতা আপনার যন্ত্রের থিম মানছে। “ডার্ক” চাপুন — পাতা গাঢ় হলো, বোতামগুলো নিজেরাও গাঢ় হলো, নিচে লেখা এলো থিম → dark; “লাইট” চাপলে উল্টো। খেয়াল করুন, script কোনো রং ছোঁয় না — শুধু attribute বসায় বা মুছে দেয়। Screen reader-কে কোন বোতাম চালু তা জানাতে aria-pressed ব্যবহার হয়েছে, আর চোখে বোঝাতে outline. চেষ্টা করুন: (১) CSS-এ [data-theme="sepia"]-এর জন্য একটি চতুর্থ থিম আর একটি বোতাম যোগ করুন। (২) প্রিভিউর “আগের কোড” চেপে আবার চালান — পছন্দ ভুলে গেছে, কারণ এখানে মান শুধু একটি variable-এ ছিল। আসল সাইটে মনে রাখার কাজটি করে localStorage — এই প্রিভিউর নিরাপদ বাক্সে (sandbox) সেটি বন্ধ, তাই নিচে আসল কোডটি আলাদা করে দেখাচ্ছি।

পছন্দ মনে রাখা — localStorage

localStorage হলো ব্রাউজারের ছোট একটি স্থায়ী খাতা — প্রতিটি সাইটের (আসলে প্রতিটি origin-এর) আলাদা। ব্রাউজার বন্ধ করে আবার খুললেও লেখা থাকে। বিস্তারিত জানতে JavaScript কোর্সের Web Storage পর্ব দেখুন। থিমের জন্য দরকার মাত্র দুটি কাজ:

function setTheme(t) {               // t = 'system' | 'light' | 'dark'
  const root = document.documentElement;
  if (t === 'system') delete root.dataset.theme;
  else root.dataset.theme = t;
  try {                               // প্রাইভেট মোড বা কড়া সেটিংয়ে localStorage না-ও থাকতে পারে
    if (t === 'system') localStorage.removeItem('theme');
    else localStorage.setItem('theme', t);
  } catch (e) { /* না পারলে শুধু এই পাতার জন্য থিম থাকবে */ }
}

“সিস্টেম” বাছলে মান মুছে দেওয়াটা গুরুত্বপূর্ণ: তখন সিস্টেমের থিম বদলালে পাতাও বদলাবে। কিন্তু আসল কৌশল হলো পাতা খোলার সময় সেই মান পড়া — আর কখন পড়ছেন, সেটাই সব পার্থক্য গড়ে দেয়। নিচের ডায়াগ্রামে দেখুন:

ভুল পথ · ধাপ ১HTML পড়া শুরু
<head>  <link rel="stylesheet" href="style.css">  </head>
<body>  … পুরো পাতা …
        <script src="theme.js"></script>   ← একেবারে শেষে
</body>
ভুল পথ · ধাপ ২প্রথম আঁকা (first paint)
data-theme নেই → লাইট টোকেন → পাতা আঁকা হলো সাদা
রহিম স্টোর · আজকের অফার…
ভুল পথ · ধাপ ৩শেষে script চলল
localStorage.theme = "dark" → data-theme="dark" → আবার আঁকা
ফল: এক ঝলক সাদা, তারপর কালো
রহিম স্টোর · আজকের অফার… ⚡
ঠিক পথ · ধাপ ৪head-এ, CSS-এর আগে
<head>
  <meta name="color-scheme" content="light dark">
  <script> /* ৫ লাইনের ছোট script */ </script>
  <link rel="stylesheet" href="style.css">
</head>
ঠিক পথ · ধাপ ৫আঁকার আগেই attribute
<html data-theme="dark">   ← body তৈরির আগেই বসে গেছে
CSS এলো → ডার্ক টোকেন মিলল → প্রথম আঁকাই গাঢ়
রহিম স্টোর · আজকের অফার… ✓
  1. ভুল পথ — script একেবারে শেষে। অনেক টিউটোরিয়াল (আর AI) থিমের script রাখে </body>-এর ঠিক আগে। ব্রাউজার ওপর থেকে নিচে পড়ে, তাই CSS পায় আগে, থিমের script পায় সবার শেষে।
  2. প্রথম আঁকা হয়ে যায় লাইটে। CSS আসার পর ব্রাউজার যত দ্রুত সম্ভব পাতা আঁকে (first paint). তখনো কেউ data-theme বসায়নি, তাই লাইট টোকেন দিয়েই পুরো পাতা সাদা আঁকা হয়।
  3. তারপর হঠাৎ বদল — ঝলক। শেষে script চলে, localStorage-এ dark পায়, attribute বসায়, পাতা কালো হয়। মাঝের কয়েকশো মিলিসেকেন্ডের সাদা পর্দাকে বলে flash of wrong theme. অন্ধকারে চোখে লাগে, আর দেখতে অপেশাদার।
  4. ঠিক পথ — head-এ ছোট inline script. <head>-এ, stylesheet-এর আগে, ফাইল ছাড়া সরাসরি লেখা কয়েক লাইনের script. এটি parser-কে খুব সামান্য সময়ের জন্য আটকায় — একটি render-blocking কাজ — কিন্তু এত ছোট যে ক্ষতি নগণ্য, আর লাভ বিশাল।
  5. প্রথম আঁকাই সঠিক রঙে। body তৈরির আগেই <html>-এ data-theme বসে যায়। CSS এলে ডার্ক টোকেনই মেলে, তাই প্রথম পর্দাই গাঢ় — কোনো ঝলক নেই। বোতামে ক্লিক সামলানোর বাকি বড় JavaScript পরে defer করে আনলেই চলে।

সেই ছোট script-টি এমন:

<head>
  <meta charset="utf-8">
  <meta name="color-scheme" content="light dark">
  <script>
    try {
      const t = localStorage.getItem('theme');
      if (t === 'light' || t === 'dark') document.documentElement.dataset.theme = t;
    } catch (e) {}
  </script>
  <link rel="stylesheet" href="style.css">
  <script src="theme-toggle.js" defer></script>
</head>

কয়েকটি বাস্তব সতর্কতা। (১) মান যাচাই করুন ('light' বা 'dark' কি না) — localStorage-এর লেখা অন্য কোড বা ব্যবহারকারী বদলে দিতে পারেন। (২) সাইটে কড়া Content Security Policy থাকলে inline script-এর জন্য nonce বা hash লাগবে। (৩) সার্ভার থেকে পাতা তৈরি হলে (PHP, Node) পছন্দটি cookie-তে রেখে সার্ভারই <html data-theme="dark"> লিখে পাঠাতে পারে — তখন script ছাড়াই ঝলক নেই। (৪) বোতামের লেখা স্পষ্ট রাখুন (“ডার্ক থিম”), শুধু চাঁদ-সূর্যের আইকন নয়; আইকন দিলে aria-label দিন।

আরেকটি ছোট সংযোজন: পর্ব ১৩-এ দেখা theme-color meta-তে media attribute দিলে মোবাইল ব্রাউজারের ওপরের বারও থিম মেনে চলে — <meta name="theme-color" content="#18181b" media="(prefers-color-scheme: dark)">. তবে এটি সিস্টেমের পছন্দ দেখে, আপনার বোতামের নয়; বোতামের সাথে মেলাতে JavaScript দিয়ে content বদলাতে হয়।

৭ · ডার্ক প্যালেটের নকশা — উল্টো রং নয়, নতুন করে ভাবা

সবচেয়ে সহজ ভুল হলো “সাদাকে কালো, কালোকে সাদা” করে দেওয়া, আর ব্র্যান্ডের রং যেমন ছিল তেমন রাখা। ফল হয় চোখ-ধাঁধানো, কিংবা পড়াই যায় না। ভালো ডার্ক প্যালেটের চারটি নিয়ম:

  • খাঁটি কালো নয়, গাঢ় ধূসর। #000-এর ওপর #fff-এর contrast সর্বোচ্চ (২১:১) — এত বেশি যে লেখার কিনারা যেন জ্বলে ওঠে আর ছড়িয়ে পড়ে, বিশেষ করে astigmatism থাকলে। #121212 থেকে #1e1e1e-এর মতো গাঢ় ধূসর পটভূমি আর পুরো সাদার বদলে একটু ধূসর লেখা (#e4e4e7) বেশি আরামদায়ক। Google-এর Material Design নির্দেশিকাও বহু বছর ধরে এমন গাঢ় ধূসর সুপারিশ করে এসেছে।
  • রঙের তীব্রতা কমান (desaturate). সাদা পটভূমিতে মানানসই গাঢ়, তীব্র রং গাঢ় পটভূমিতে হয় মিশে যায়, নয় চোখে কাঁপে। ডার্কে একই রঙের হালকা, কম তীব্র সংস্করণ নিন — গাঢ় সবুজ #15803d-এর বদলে #4ade80. পর্ব ২০-এর oklch()-এ এটা সহজ: hue একই রেখে lightness বাড়ান, chroma কমান।
  • উচ্চতা (elevation) মানে একটু হালকা। লাইট থিমে ওপরে ভাসা কার্ড বা মেনু বোঝানো হয় ছায়া দিয়ে। গাঢ় পটভূমিতে ছায়া প্রায় দেখাই যায় না, তাই ডার্কে elevation বোঝানো হয় পটভূমি একটু একটু হালকা করে — পাতা #121212, কার্ড #1e1e1e, তার ওপরের মেনু #2a2a2a.
  • দুই থিমেই আলাদা করে contrast মাপুন। লাইটে পাস করা রং ডার্কে ফেল করতে পারে। WCAG 2.2-এর AA স্তরে সাধারণ লেখার contrast ratio অন্তত ৪.৫:১, বড় লেখা আর বোতামের কিনারা, আইকনের মতো UI অংশে ৩:১. প্রতিটি থিম আলাদা একটি “নকশা” — দুটোকেই পাস করতে হবে।
<style>
  .panel { font-family: sans-serif; padding: 12px; border-radius: 10px;
           margin-bottom: 10px; }
  .panel div { padding: 8px 10px; margin-top: 8px; border-radius: 8px; }
  /* ভুল: খাঁটি কালো, খাঁটি সাদা, লাইটের সেই তীব্র রং */
  .bad { background: #000000; color: #ffffff; }
  .bad div { background: #000000; box-shadow: 0 2px 8px rgb(0 0 0 / .5); }
  .bad b { color: #1d4ed8; }
  /* ভালো: গাঢ় ধূসর, একটু নরম লেখা, হালকা করা রং, স্তরে স্তরে উচ্চতা */
  .good { background: #121212; color: #e4e4e7; }
  .good .e1 { background: #1e1e1e; }
  .good .e2 { background: #2a2a2a; }
  .good b { color: #93c5fd; }
</style>
<div class="panel bad">ভুল: সব একই কালো
  <div class="e1">কার্ড — <b>বিস্তারিত দেখুন</b>
    <div class="e2">মেনু — ছায়া দেখা যায় না</div></div></div>
<div class="panel good">ভালো: স্তরে স্তরে হালকা
  <div class="e1">কার্ড — <b>বিস্তারিত দেখুন</b>
    <div class="e2">মেনু — ওপরের স্তর স্পষ্ট</div></div></div>

ওপরের প্যানেলে কার্ড আর মেনু কালোয় মিশে গেছে — ছায়া দেওয়া আছে, কিন্তু কালোর ওপর কালো ছায়া অদৃশ্য; নীল লিংক প্রায় পড়াই যায় না, আর সাদা লেখা চোখে ধাক্কা দেয়। নিচের প্যানেলে প্রতিটি স্তর একটু হালকা, তাই কোনটি কার ওপরে তা এক নজরে বোঝা যায়, আর হালকা নীল লিংক স্পষ্ট। চেষ্টা করুন: (১) .good b-কে #1d4ed8 করুন — লাইটে যে রং দারুণ, ডার্কে সেটি কতটা ডুবে যায় দেখুন। (২) .good .e2-কে #121212 করুন — মেনুটি পাতার সাথে মিশে গেল।

চোখে দেখার পাশাপাশি সংখ্যাতেও যাচাই করা দরকার। নিচের কোডটি WCAG-এর সূত্র দিয়ে কয়েক জোড়া রঙের contrast ratio হিসাব করে — লাইট আর ডার্ক দুই থিমের জোড়া পাশাপাশি:

function lum(hex) {                     // WCAG-এর relative luminance
  const [r, g, b] = hex.match(/\w\w/g).map(h => parseInt(h, 16) / 255)
    .map(c => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function ratio(a, b) {
  const [hi, lo] = [lum(a), lum(b)].sort((x, y) => y - x);
  return (hi + 0.05) / (lo + 0.05);
}
const pairs = [
  ['লাইট: লেখা', '#1f2937', '#ffffff'],
  ['লাইট: সবুজ বোতাম/লিংক', '#15803d', '#ffffff'],
  ['ডার্ক: লেখা', '#e4e4e7', '#18181b'],
  ['ডার্ক: সেই একই গাঢ় সবুজ', '#15803d', '#18181b'],
  ['ডার্ক: হালকা সবুজ', '#4ade80', '#18181b'],
  ['খাঁটি সাদা-কালো', '#ffffff', '#000000'],
];
for (const [label, fg, bg] of pairs) {
  const r = ratio(fg, bg);
  const verdict = r >= 4.5 ? 'AA পাস' : r >= 3 ? 'শুধু বড় লেখা/UI' : 'ফেল';
  console.log(label + ': ' + r.toFixed(2) + ':1 → ' + verdict);
}

ফলাফলে দেখবেন, লাইট থিমের গাঢ় সবুজ (#15803d) সাদার ওপর AA পাস করে, কিন্তু সেই একই রং গাঢ় পটভূমিতে নেমে আসে মাত্র ৩.৫:১-এ — সাধারণ লেখার জন্য AA-তে ফেল, শুধু বড় লেখা বা UI-র ৩:১ কোনোমতে পার হয় — অংশ ৪-এর পরীক্ষায় বোতাম কেন মিশে গিয়েছিল, এটাই তার সংখ্যাগত কারণ। হালকা সবুজ #4ade80 ডার্কে অনায়াসে পাস। আর খাঁটি সাদা-কালো ঠিক ২১:১ — সর্বোচ্চ, কিন্তু আরামদায়ক নয়। চেষ্টা করুন: তালিকায় নিজের সাইটের রঙের একটি জোড়া যোগ করে চালান। দৈনন্দিন কাজে এর জন্য কোড লাগে না — DevTools-এর color picker-এ রঙে ক্লিক করলেই contrast ratio দেখায়।

৮ · ছবি, লোগো আর forced colors

রং তো টোকেনে বদলাল, কিন্তু ছবি? সাদা পটভূমির জন্য বানানো গাঢ় সবুজ লোগো ডার্ক পাতায় প্রায় অদৃশ্য। আর বড় উজ্জ্বল ছবি অন্ধকার পাতায় টর্চের মতো জ্বলে। তিনটি কৌশল আছে, প্রতিটির নিজস্ব সতর্কতাসহ:

  1. আলাদা ছবি: পর্ব ৩২-এর <picture>-এ <source media="(prefers-color-scheme: dark)"> দিলে ডার্ক সিস্টেমে আলাদা ফাইল নামে — লোগোর জন্য সবচেয়ে নির্ভরযোগ্য।
  2. SVG-র ভেতরে currentColor: একরঙা আইকন বা লোগোর রং যদি currentColor হয়, সেটি লেখার রং মেনে নিজেই থিম বদলায় (বিস্তারিত পর্ব ৪৪-এ)।
  3. filter — সাবধানে: ডার্কে ফটোকে filter: brightness(.85) দিয়ে একটু নিষ্প্রভ করা ভালো কৌশল। কিন্তু invert(1) সব রং উল্টে দেয় — কালো লোগো সাদা হয় ঠিকই, তবে লাল হয় আকাশি, সবুজ হয় বেগুনি-গোলাপি।
<style>
  body { font-family: sans-serif; }
  @media (prefers-color-scheme: dark) {
    body { background: #18181b; color: #e4e4e7; }
    .photo { filter: brightness(.85); }
  }
  .photo { width: 160px; height: 60px; border-radius: 8px;
           background: linear-gradient(#fde68a, #f97316); }
  .inv { filter: invert(1); }
  .inv-hue { filter: invert(1) hue-rotate(180deg); }
</style>
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='170' height='34'%3E%3Ctext x='0' y='26' font-family='sans-serif' font-size='24' font-weight='700' fill='%2386efac'%3ERahim Store%3C/text%3E%3C/svg%3E">
  <img alt="রহিম স্টোর" width="170" height="34" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='170' height='34'%3E%3Ctext x='0' y='26' font-family='sans-serif' font-size='24' font-weight='700' fill='%23166534'%3ERahim Store%3C/text%3E%3C/svg%3E">
</picture>
<div class="photo" role="img" aria-label="সূর্যাস্তের ছবির বদলে একটি রঙিন বাক্স"></div>
<p>পতাকা: আসল · invert(1) · invert + hue-rotate</p>
<svg width="60" height="36" aria-hidden="true"><rect width="60" height="36" fill="#006a4e"/><circle cx="27" cy="18" r="12" fill="#f42a41"/></svg>
<svg class="inv" width="60" height="36" aria-hidden="true"><rect width="60" height="36" fill="#006a4e"/><circle cx="27" cy="18" r="12" fill="#f42a41"/></svg>
<svg class="inv-hue" width="60" height="36" aria-hidden="true"><rect width="60" height="36" fill="#006a4e"/><circle cx="27" cy="18" r="12" fill="#f42a41"/></svg>

লাইট সিস্টেমে গাঢ় সবুজ “Rahim Store” লোগো দেখছেন, ডার্ক সিস্টেমে হালকা সবুজ — দুটি আলাদা ছবি, ব্রাউজার নিজে বেছেছে। ডার্কে রঙিন বাক্সটি (ফটোর প্রতিনিধি) একটু নিষ্প্রভ হয়। নিচের তিনটি পতাকা দেখুন: invert(1)-এ বাংলাদেশের পতাকা হয়ে গেল গোলাপি জমিনে আকাশি বৃত্ত — ব্র্যান্ড বা জাতীয় প্রতীকে এটি একেবারে অগ্রহণযোগ্য। hue-rotate(180deg) যোগ করলে রঙের ধরন অনেকটা ফেরে, কিন্তু উজ্জ্বলতা উল্টেই থাকে। চেষ্টা করুন: দুই জায়গার prefers-color-scheme: dark-কে light করুন (লাইট সিস্টেমে ডার্ক রূপ দেখার কৌশল)।

একটি লুকানো দ্বন্দ্ব: <picture>-এর media আর theme-color সবসময় সিস্টেমের পছন্দ দেখে, আপনার পাতার data-theme বোতামের নয়। ব্যবহারকারী লাইট সিস্টেমে আপনার বোতামে “ডার্ক” বাছলে লোগো লাইটই থাকবে। সমাধান: বোতাম-নির্ভর সাইটে লোগো SVG-তে currentColor দিয়ে বা CSS-এর টোকেন দিয়ে আঁকুন, অথবা JavaScript দিয়ে ছবির উৎস বদলান।

Forced colors — ব্যবহারকারীর রং সবার ওপরে

কিছু মানুষের জন্য dark mode-ও যথেষ্ট নয় — কম দৃষ্টিশক্তি বা আলোর প্রতি তীব্র সংবেদনশীলতার কারণে তাঁরা Windows-এর contrast theme-এর মতো সেটিং চালু রাখেন। তখন ব্রাউজার forced colors mode-এ চলে যায়: আপনার দেওয়া প্রায় সব রং, পটভূমির ছবি আর ছায়া বাদ দিয়ে ব্যবহারকারীর বেছে নেওয়া অল্প কয়েকটি রং বসিয়ে দেয়। এখানে আপনার কাজ লড়াই করা নয়, সাহায্য করা:

@media (forced-colors: active) {
  /* রং দিয়ে বোঝানো সীমানা হারিয়ে যায় — স্বচ্ছ বর্ডার দিন, তখন সেটি দেখা যাবে */
  .card   { border: 1px solid transparent; }
  /* রঙের বদলে system color — ব্যবহারকারীর থিমের রংই নেবে */
  .badge  { border: 1px solid CanvasText; }
  .active { outline: 2px solid Highlight; }
}
/* forced-color-adjust: none — শুধু বিরল ক্ষেত্রে, যেমন রঙের নমুনা দেখানোর ঘর */

এখানে system color-এর নামগুলো (Canvas, CanvasText, Highlight) আবার এলো — অংশ ২-এর সেই একই ব্যবস্থা, শুধু এবার রং ঠিক করছেন ব্যবহারকারী নিজে। প্রধান ব্রাউজারগুলো forced-colors media query চেনে; বাস্তবে এটি সবচেয়ে বেশি চালু হয় Windows-এ। অ্যাক্সেসিবল CSS-এর বাকি দিক পর্ব ৪৫-এ বিস্তারিত দেখব।

৯ · পরীক্ষা — সেটিং না বদলেই দুই থিম দেখা

প্রতিবার পরীক্ষার জন্য ফোন বা Windows-এর থিম বদলানো ঝামেলার। DevTools এটা এক ক্লিকে করে দেয়:

  1. Chrome বা Edge-এ DevTools খুলুন (F12), তারপর Ctrl+Shift+P চেপে Command Menu-তে Show Rendering লিখে Enter চাপুন।
  2. Rendering প্যানেলে Emulate CSS media feature prefers-color-scheme থেকে dark বা light বেছে নিন — পাতা সাথে সাথে বদলাবে, সিস্টেম যেমন ছিল তেমনই থাকবে।
  3. একই প্যানেলে forced-colors আর prefers-contrast-ও অনুকরণ করা যায়, আর দৃষ্টির নানা সীমাবদ্ধতা (যেমন বর্ণান্ধতা) অনুকরণের ব্যবস্থাও আছে।
  4. Firefox-এ Inspector-এর Rules অংশের ওপরে আলো ও অন্ধকারের দুটি বোতাম দিয়ে একই কাজ হয়।

পরীক্ষার একটি ছোট তালিকা মাথায় রাখুন: দুই থিমে প্রতিটি পাতা একবার; ফর্মের ঘর, ড্রপডাউন আর scrollbar; লোগো আর আইকন; focus-এর দাগ (ডার্কে নীল outline হারিয়ে যায় কি না); আর পাতা reload করে ঝলক আছে কি না — ধীর নেটওয়ার্ক অনুকরণ করলে (Network প্যানেলে “Slow 4G”) ঝলক আরও স্পষ্ট ধরা পড়ে। শেষে Lighthouse-এর অ্যাক্সেসিবিলিটি পরীক্ষা দুই থিমে আলাদা করে চালান, কারণ contrast-এর ফল দুই জায়গায় আলাদা।

পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা ফ্রন্ট-এন্ড ইন্টারভিউর পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে:

  • color-scheme আর prefers-color-scheme-এর পার্থক্য কী? — প্রথমটি পাতা কী পারে তা ব্রাউজারকে জানায় (ডিফল্ট রং, ফর্ম, scrollbar বদলায়); দ্বিতীয়টি ব্যবহারকারী কী চান তা CSS-কে জিজ্ঞেস করার media query.
  • Dark mode-এ CSS variable কেন? — কম্পোনেন্ট semantic token ব্যবহার করে, থিম শুধু টোকেনের মান বদলায়; রং একবার লেখা হয়, ভুলে যাওয়ার সুযোগ কমে।
  • Theme toggle-এ সাদা ঝলক কেন হয়, সমাধান কী? — পছন্দ পড়ার script প্রথম আঁকার পরে চলে; <head>-এ CSS-এর আগে ছোট inline script বা সার্ভার থেকে attribute.
  • light-dark() কাজ না করলে প্রথমে কী দেখবেন? — color-scheme দেওয়া আছে কি না; এটি ছাড়া সবসময় প্রথম রং আসে।
  • ডার্ক থিমে খাঁটি কালো কেন এড়ানো হয়? — অতিরিক্ত contrast-এ লেখা ছড়ায়, elevation বোঝানো যায় না; গাঢ় ধূসর আর হালকা স্তর ভালো।

১০ · AI-এর যুগে — dark mode-এর কাজ AI-কে দিলে কী দেখবেন

“আমার সাইটে dark mode যোগ করো” বললে AI কয়েক সেকেন্ডে একটি বোতাম আর কিছু CSS দেয়, আর প্রথম দেখায় কাজও করে। কিন্তু AI নিজের কোড ধীর নেটওয়ার্কে reload করে ঝলক দেখে না, ডার্কে input বাক্স সাদা থেকে গেল কি না খেয়াল করে না, আর contrast মাপে না। ইন্টারনেটে বহু বছরের পুরোনো dark mode টিউটোরিয়াল থেকে শেখায় সে প্রায়ই পুরোনো কৌশল দেয়।

AI সাধারণত যেসব ভুল করে

AI-এর ভুলআপনি যা দেখবেনঠিক উপায়
color-scheme নেই, শুধু body-র রং বদলডার্কে input, select, scrollbar সাদা:root { color-scheme: light dark } + meta
থিমের script </body>-এর আগে বা DOMContentLoaded-এreload করলে সাদা ঝলক<head>-এ CSS-এর আগে ছোট inline script
শুধু বোতাম, সিস্টেমের পছন্দ উপেক্ষাডার্ক ফোনেও প্রথমবার সাদা সাইটডিফল্ট “সিস্টেম”, তারপর লাইট/ডার্ক
html { filter: invert(1) } দিয়ে “dark mode”ছবি, লোগো, পতাকার রং উল্টো; ধীর পাতাটোকেন দিয়ে আসল ডার্ক প্যালেট
#000 পটভূমি, লাইটের সেই তীব্র ব্র্যান্ড রংচোখ-ধাঁধানো লেখা, লিংক পড়া যায় নাগাঢ় ধূসর, হালকা ও কম তীব্র রং, contrast মাপা
প্রতিটি selector-এ .dark .card {…}, অনেক !importantনতুন কম্পোনেন্টে ডার্ক রং ভুলে যাওয়াsemantic token, কম্পোনেন্টে শুধু var()
light-dark() দিয়েছে, কিন্তু color-scheme দেয়নিসবসময় লাইট রংআগে color-scheme
localStorage try/catch ছাড়া, মান যাচাই ছাড়াপ্রাইভেট মোডে বা কড়া সেটিংয়ে script errortry { … } catch {} আর মানের তালিকা যাচাই

AI-কে কাজ দেওয়ার সময় শর্তগুলো আগেই বলে দিন, আর তাকে প্রতিটি সিদ্ধান্তের কারণ লিখতে বলুন:

তুমি একজন অভিজ্ঞ ফ্রন্ট-এন্ড ডেভেলপার। আমার বাংলা অনলাইন দোকানের সাইটে লাইট, ডার্ক আর "সিস্টেম" — তিন বিকল্পের থিম যোগ করতে চাই। শর্ত: ১. :root-এ color-scheme আর দুটোই দাও। ২. রং থাকবে দুই স্তরের CSS variable-এ: কাঁচা রং (--green-700) আর semantic token (--surface, --text, --accent, --border); কম্পোনেন্টে শুধু var(). ৩. টোকেনের মান light-dark() দিয়ে, আর পুরোনো ব্রাউজারের জন্য fallback দেখাও। ৪. data-theme="light"/"dark" দিয়ে color-scheme বদলাবে; "সিস্টেম" বাছলে attribute আর localStorage-এর মান মুছে যাবে। ৫. ঝলক এড়াতে head-এ CSS-এর আগে ছোট inline script; localStorage try/catch-এ, মান যাচাইসহ। ৬. বোতামে aria-pressed আর স্পষ্ট বাংলা লেখা; filter: invert বা !important ব্যবহার করবে না। শেষে লাইট আর ডার্ক — দুই থিমে লেখা আর accent রঙের contrast ratio হিসাব করে দেখাও, WCAG AA পাস কি না।

AI-এর দেওয়া dark mode হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে CSS লেখক থেকে নকশা-চিন্তক বানাবে।

প্র ০১Dark mode কি সত্যিই চোখের জন্য ভালো আর ব্যাটারি বাঁচায়? তাহলে সব সাইট শুধু ডার্ক করে দিলেই তো হয়?

দুটো দাবিই আংশিক সত্য, আর ঠিক এই “আংশিক”টুকু বোঝাই নকশার আসল কাজ।

  • পড়ার গতি ও স্পষ্টতা: দৃষ্টি-গবেষণায় বহু বছর ধরে একটি ফল বারবার এসেছে — স্বাভাবিক আলোয় বেশিরভাগ মানুষ হালকা পটভূমিতে গাঢ় লেখা (একে বলে positive polarity) একটু দ্রুত ও নির্ভুল পড়েন। কারণ উজ্জ্বল পর্দায় চোখের মণি ছোট হয়, ফলে ছবি তীক্ষ্ণ হয় — ক্যামেরার aperture ছোট করার মতো। অন্ধকার ঘরে আবার উজ্জ্বল সাদা পর্দা অস্বস্তিকর, সেখানে ডার্ক আরামদায়ক।
  • সবার চোখ এক নয়: astigmatism থাকলে কালোর ওপর উজ্জ্বল লেখা ছড়িয়ে যাওয়ার অভিযোগ সাধারণ; অন্যদিকে আলোর প্রতি সংবেদনশীল বা মাইগ্রেনের রোগীদের অনেকে ডার্ক ছাড়া পর্দায় তাকাতেই পারেন না। একই নকশা দুই দলকে বিপরীতভাবে প্রভাবিত করে।
  • ব্যাটারি: OLED পর্দায় গাঢ় পিক্সেলে কম বিদ্যুৎ লাগে — এটি পদার্থবিদ্যার সত্য। কিন্তু সাশ্রয়ের পরিমাণ উজ্জ্বলতার ওপর খুব নির্ভর করে: বেশি উজ্জ্বলতায় উল্লেখযোগ্য, সাধারণ ঘরোয়া উজ্জ্বলতায় সামান্য। আর LCD পর্দায় (অনেক ল্যাপটপ ও সস্তা ফোনে) পেছনের আলো সবসময় পুরো জ্বলে, তাই প্রায় কোনো লাভ নেই।
  • ঘুম আর নীল আলো: রাতে কম আলো উপকারী — কিন্তু এর জন্য উজ্জ্বলতা কমানো আর সিস্টেমের night light বেশি কার্যকর; dark mode একা সমাধান নয়।

তাই “শুধু ডার্ক” সাইট একটি দলকে আরাম দেয়, আরেক দলকে কষ্ট। পেশাদার সমাধান তিন ধাপের — সিস্টেমের পছন্দ ডিফল্ট হিসেবে মানা, পাতায় বদলানোর বোতাম, আর দুই থিমেই সমান যত্ন (contrast, ফর্ম, ছবি)। কিছু পণ্য সচেতনভাবে একটিই থিম রাখে — যেমন ভিডিও এডিটর বা সিনেমা দেখার অ্যাপ সাধারণত সবসময় গাঢ়, যাতে ছবি থেকে চোখ না সরে। সেটাও যুক্তিসঙ্গত সিদ্ধান্ত, যদি কাজটির প্রকৃতি তা দাবি করে।

মূল উপলব্ধি: অ্যাক্সেসিবিলিটিতে “সবার জন্য সেরা একটি” প্রায় কখনো থাকে না। ভালো নকশা ব্যবহারকারীর হাতে পছন্দ তুলে দেয়, আর প্রতিটি পছন্দকে পূর্ণ মর্যাদা দেয়।

প্র ০২ব্রাউজার নিজেই যদি যেকোনো পাতা অন্ধকার করে দিতে পারে (auto dark, Dark Reader-এর মতো extension), তাহলে ডেভেলপারকে কষ্ট করে ডার্ক থিম বানাতে হবে কেন?

ব্রাউজার বা extension-এর এই কৌশলকে বলে auto dark mode বা algorithmic darkening — পাতার রংগুলো হিসাব কষে উল্টে বা গাঢ় করে দেওয়া। Android-এর Chrome-এ এমন একটি ব্যবস্থা আছে, আর Dark Reader-এর মতো extension বহু লক্ষ মানুষ ব্যবহার করেন। এটি প্রমাণ করে মানুষ ডার্ক চান — কিন্তু সমাধান হিসেবে এর সীমা স্পষ্ট:

  • অর্থ বোঝে না: অ্যালগরিদম জানে না কোন লাল রং “ভুল হয়েছে” বোঝাচ্ছে আর কোনটি ব্র্যান্ডের, কোন ধূসর বাক্সটি কার্ড আর কোনটি নিষ্ক্রিয় বোতাম। ফলে রঙের অর্থ হারিয়ে যায়, কখনো contrast-ও ভেঙে যায়।
  • ছবি আর চার্ট: লোগো, ডায়াগ্রাম, স্ক্রিনশট, মানচিত্র — হয় উল্টে বিকৃত হয়, নয় অন্ধকার পাতায় সাদা জানালার মতো জ্বলে। পতাকার উদাহরণে দেখেছেন, invert কী করে।
  • গতি: extension-কে পুরো পাতার CSS পড়ে নতুন CSS বানাতে হয়, যা ভারী পাতায় ধীর, আর পাতা খোলার সময় ঝলকও হয়।

এজন্যই স্পেসিফিকেশন একটি “চুক্তি” রেখেছে: পাতা color-scheme: light dark ঘোষণা করলে ব্রাউজার বোঝে “এই পাতা নিজেই ডার্ক সামলায়”, তখন সে সাধারণত নিজের অ্যালগরিদম চালায় না। আর only light দিয়ে পাতা স্বয়ংক্রিয় অন্ধকার থেকে বাদ পড়তে চায় — যেমন একটি রং-মেলানোর টুল, যেখানে রং বদলানো মানেই ভুল তথ্য।

ইতিহাসও এখানে শিক্ষণীয়। ২০১৮–২০১৯ সালে অপারেটিং সিস্টেমগুলো ডার্ক আনার পর কয়েক বছর ধরে ওয়েব পিছিয়ে ছিল — অ্যাপ অন্ধকার, কিন্তু ব্রাউজারে সাইট খুললেই সাদা। সেই ফাঁক পূরণ করতেই auto dark আর extension-এর জন্ম। যত বেশি সাইট নিজে ভালো ডার্ক থিম দেবে, এই জোড়াতালির দরকার তত কমবে।

মূল উপলব্ধি: স্বয়ংক্রিয় সমাধান রং বোঝে, অর্থ বোঝে না। যে নকশায় রং তথ্য বহন করে, সেখানে মানুষের নেওয়া সিদ্ধান্তের বিকল্প নেই।

প্র ০৩শুধু prefers-color-scheme যথেষ্ট, নাকি নিজের toggle বোতামও দরকার? দুটো রাখলে কোন কোন জটিলতা তৈরি হয়?

শুধু media query দিলে কোড সবচেয়ে সরল, কোনো JavaScript নেই, ঝলকেরও ভয় নেই। কিন্তু এটি ধরে নেয় ব্যবহারকারীর সিস্টেম-সেটিং প্রতিটি সাইটে তাঁর ইচ্ছা। বাস্তবে তা নয় — কেউ ফোন লাইটে রাখেন, কিন্তু রাতে লম্বা প্রবন্ধ ডার্কে পড়তে চান; কেউ অফিসের কম্পিউটারের সেটিং বদলাতেই পারেন না। তাই পড়ার সাইট, ড্যাশবোর্ড আর দীর্ঘ ব্যবহারের অ্যাপে বোতাম রাখা প্রায় নিয়ম হয়ে গেছে।

দুটো রাখলে যে জটিলতাগুলো আসে:

  • দুই নয়, তিন অবস্থা: লাইট, ডার্ক আর “সিস্টেম মানো”। শুধু দুই-অবস্থার সুইচ দিলে একবার চাপার পর ব্যবহারকারী আর কখনো সিস্টেমের সাথে স্বয়ংক্রিয় মিল ফিরে পান না।
  • ঝলক: পছন্দ ব্রাউজারে থাকলে head-এর inline script লাগে, যা কড়া Content Security Policy-র সাথে ঝামেলা করে।
  • যা CSS-এর বাইরে: <picture>-এর media, theme-color meta — এরা সিস্টেম দেখে, আপনার বোতাম নয়। এগুলোকে আলাদা করে মেলাতে হয়।
  • কোথায় মনে রাখবেন: localStorage এক যন্ত্রে, এক ব্রাউজারে সীমাবদ্ধ। লগইন-করা সাইটে পছন্দ অ্যাকাউন্টে রাখলে সব যন্ত্রে মেলে; তখন সার্ভার cookie দেখে সরাসরি সঠিক attribute-সহ HTML পাঠাতে পারে, ফলে script ছাড়াই ঝলক নেই।
  • বোতামের অ্যাক্সেসিবিলিটি: শুধু সূর্য-চাঁদের আইকন অনেকে বোঝেন না; লেখা, aria-pressed বা radio group, আর কীবোর্ডে চালানো যাওয়া দরকার।

আধুনিক CSS এই জটিলতা অনেক কমিয়েছে: বোতাম শুধু color-scheme বদলায়, আর সব রং light-dark()-এ থাকায় media query আর attribute-এর জন্য রং দুইবার লিখতে হয় না। ফর্ম আর scrollbar-ও একই সাথে বদলায়।

মূল উপলব্ধি: ডিফল্ট হিসেবে সিস্টেম, ইচ্ছা হলে বদল — এই দুই স্তরের নকশা প্রায় সব ব্যবহারকারী-পছন্দের ক্ষেত্রেই খাটে: ভাষা, লেখার আকার, অ্যানিমেশন।

প্র ০৪বড় কোম্পানির design system কেন রংকে কয়েক স্তরের token-এ ভাগ করে? শুধু --green, --white রাখলে সমস্যা কী?

ধরুন আপনার কোডে --white: #fff আছে, আর কার্ডের পটভূমি var(--white). ডার্ক থিম আনতে গিয়ে দুটি পথ — --white-এর মান গাঢ় করা (তখন নামটি মিথ্যা, নতুন সহকর্মী বিভ্রান্ত হবেন), অথবা প্রতিটি কম্পোনেন্ট খুঁজে খুঁজে বদলানো (শত শত জায়গা, ভুল নিশ্চিত)। সমস্যার মূল হলো নামটি রঙের চেহারা বলে, কাজ বলে না।

তাই বড় design system সাধারণত তিন স্তরে ভাবে:

  • Primitive (কাঁচা): --green-700, --zinc-900 — রঙের পুরো তালিকা, থিমে বদলায় না।
  • Semantic: --surface, --text-muted, --accent, --danger — কাজের নাম; প্রতিটি থিম এদের কোন primitive দেবে তা ঠিক করে।
  • Component: --button-bg, --card-border — প্রয়োজনে একটি কম্পোনেন্টের জন্য আলাদা নিয়ন্ত্রণ, সাধারণত semantic token থেকে নেওয়া।

এর সুফল বাস্তবে দেখা গেছে। GitHub ২০২০ সালের শেষে dark mode আনে, আর পরে একই token-কাঠামোর ওপর আরও কয়েকটি থিম যোগ করে — নরম “dimmed” ডার্ক, বেশি contrast-এর থিম, আর বর্ণান্ধতা (colorblindness) বিবেচনায় বানানো থিম। কম্পোনেন্টের কোড না ছুঁয়ে শুধু semantic token-এর নতুন মান-সেট — এটাই এত থিম সম্ভব করেছে। Google-এর Material Design-ও একই ধারায় “surface”, “on-surface”, “primary”-র মতো ভূমিকা-ভিত্তিক নামে রং সাজায়। ডিজাইনার আর ডেভেলপারের মধ্যে token আদান-প্রদানের জন্য W3C-র একটি community group একটি সাধারণ ফাইল-ফরম্যাটও তৈরি করেছে, যাতে Figma-র মতো ডিজাইন টুল থেকে সরাসরি CSS variable তৈরি করা যায়।

একটি সতর্কতা: স্তর বেশি হলে খোঁজা কঠিন হয়। ছোট সাইটে দুই স্তর (primitive + semantic) প্রায়ই যথেষ্ট; component token তখনই আনুন, যখন সত্যিই দরকার পড়ে।

মূল উপলব্ধি: নাম দিন “কী কাজে”, “কেমন দেখতে” নয়। তাহলে চেহারা বদলালেও কোড সত্য থাকে — এটি শুধু রঙের নয়, প্রোগ্রামিংয়ের সব নামকরণের নিয়ম।

প্র ০৫WCAG-এর contrast সূত্র লাইট আর ডার্ক — দুই থিমে কি সমান ভালো কাজ করে? “৪.৫:১ পাস” মানেই কি পড়তে আরামদায়ক?

WCAG 2-এর contrast ratio সূত্র ((L1 + 0.05) / (L2 + 0.05), যেখানে L হলো relative luminance) ২০০৮ সালের WCAG 2.0 থেকে চলে আসছে, আর আজও আইন, সরকারি নির্দেশিকা ও চুক্তিতে এটিই মানদণ্ড। এটি সহজ, যেকোনো টুলে মাপা যায় — এর বড় শক্তি এটাই। কিন্তু কয়েকটি সীমাবদ্ধতা সুপরিচিত:

  • গাঢ় পটভূমিতে অসম আচরণ: মানুষের চোখ উজ্জ্বলতার পার্থক্য সরলরেখায় অনুভব করে না। ফলে কিছু গাঢ়-পটভূমি জোড়া সূত্রে পাস করলেও চোখে ফ্যাকাশে লাগে, আবার কিছু জোড়া ফেল করলেও বেশ পড়া যায়। ডার্ক থিমে এই অমিল বেশি চোখে পড়ে।
  • লেখার আকার ও ওজন: সূত্রটি শুধু দুটি রং দেখে। সরু, ছোট বাংলা অক্ষর — যেখানে মাত্রা, রেফ আর যুক্তাক্ষরের সূক্ষ্ম রেখা থাকে — একই অনুপাতে মোটা ইংরেজি অক্ষরের চেয়ে কঠিন। WCAG শুধু “বড় লেখা” আর “সাধারণ লেখা” দুই ভাগ করে।
  • ওপরের সীমা নেই: সূত্র বলে ২১:১ সবচেয়ে ভালো, অথচ ডার্কে খাঁটি সাদা লেখা অনেকের কাছে অস্বস্তিকর।

এসব কারণে WCAG 3-এর খসড়া নিয়ে কাজের সময় নতুন একটি পদ্ধতি আলোচিত হয়েছে — APCA নামে, যা লেখার আকার-ওজন আর পটভূমি গাঢ় না হালকা, তা হিসাবে নেয়। তবে WCAG 3 এখনো খসড়া, আর কোনো আইন বা চুক্তি এটি চায় না। তাই বাস্তব নিয়ম: WCAG 2-এর ৪.৫:১ (আর UI-র ৩:১) দুই থিমেই পূরণ করুন — এটি ন্যূনতম শর্ত; তারপর চোখে দেখে, বিশেষ করে ছোট বাংলা লেখায় ডার্ক থিম আলাদা করে পরীক্ষা করুন, আর খুব তীব্র সাদা এড়িয়ে চলুন।

মূল উপলব্ধি: মানদণ্ড হলো মেঝে, ছাদ নয়। সংখ্যা পাস করানো আইনের দাবি পূরণ করে; আরামদায়ক পড়ার অভিজ্ঞতা আসে মানুষকে দিয়ে পরীক্ষা করানো থেকে।

অনুশীলন

বেশিরভাগ কাজ দেখা আর যাচাই করার — শুধু একটিতে নিজে CSS লিখবেন।

  1. পরিচিত সাইটে দুই থিম: আপনার প্রতিদিনের কয়েকটি সাইট (একটি সংবাদ সাইট, একটি অনলাইন দোকান, এই সাইটটিও) খুলে DevTools-এর Rendering প্যানেলে prefers-color-scheme: dark অনুকরণ করুন। কোনগুলো বদলায়? যেগুলো বদলায়, সেখানে ফর্মের ঘর আর scrollbar-ও কি বদলায়?

    সাধারণত তিন রকম পাবেন: (ক) কিছুই বদলায় না — সাইটটি ডার্ক সমর্থন করে না; (খ) পটভূমি আর লেখা বদলায়, কিন্তু input বা scrollbar সাদা থেকে যায় — color-scheme দেওয়া হয়নি; (গ) সবকিছু মিলিয়ে বদলায় — সম্পূর্ণ বাস্তবায়ন। Elements প্যানেলে <html> বা :root-এর Styles দেখে color-scheme আছে কি না নিশ্চিত হন। যে সাইটে নিজের থিম বোতাম আছে, সেখানে বোতামে ক্লিক করে দেখুন <html>-এর কোন attribute বা class বদলায়।

  2. বাগ খুঁজুন: একজন লিখেছেন @media (prefers-color-scheme: dark) { body { background: #111; color: #eee; } }, আর .btn { background: light-dark(#15803d, #4ade80); }. ডার্ক সিস্টেমে পাতা গাঢ় হলো, কিন্তু অনুসন্ধানের ঘর সাদা আর বোতাম গাঢ় সবুজ। দুটি সমস্যার কারণ কী, আর এক লাইনে সমাধান কী?

    দুটির মূল কারণ একটিই — color-scheme নেই। তাই ব্রাউজার ধরে নিয়েছে পাতাটি শুধু লাইট: input নিজের ডিফল্ট সাদা রূপে আঁকা হয়েছে, আর light-dark() সবসময় প্রথম (লাইট) রং দিচ্ছে, কারণ এটি media query দেখে না, used color scheme দেখে। সমাধান: :root { color-scheme: light dark; } (সাথে <meta name="color-scheme" content="light dark">). এরপর media query-র রংগুলোও টোকেন আর light-dark()-এ নিয়ে এলে কোড আরও ছোট হবে।

  3. ঈদের থিম লিখুন: অংশ ৪-এর টোকেন-কাঠামো মেনে একটি [data-theme="eid"] থিম লিখুন, যার লাইট আর ডার্ক — দুই রূপই আছে। কম্পোনেন্টের CSS ছোঁয়া যাবে না।

    একটি সম্ভাব্য উত্তর — light-dark() ব্যবহার করায় ডার্ক রূপ আলাদা media query ছাড়াই আসে:

    [data-theme="eid"] {
      --surface: light-dark(#ecfdf5, #022c22);
      --text:    light-dark(#064e3b, #d1fae5);
      --accent:  light-dark(#047857, #34d399);
      --border:  light-dark(#6ee7b7, #065f46);
    }

    যাচাই: চারটি টোকেনই আছে কি না (একটি বাদ পড়লে সেটি আগের থিম থেকে উত্তরাধিকারে আসবে — চুপচাপ ভুল রং); দুই রূপেই --text/--surface আর বোতামের লেখা/--accent-এর contrast অংশ ৭-এর কোডে মাপুন। আর color-scheme দেওয়া আছে কি না দেখুন — নইলে ডার্ক রূপ কখনো আসবে না।

  4. ঝলক ধরুন: এমন একটি সাইট খুঁজুন যেখানে নিজের থিম বোতাম আছে। ডার্ক বেছে নিয়ে DevTools-এর Network প্যানেলে “Slow 4G” চালু করে reload করুন। সাদা ঝলক দেখলেন? Elements প্যানেলে <head> খুলে কারণ খুঁজুন।

    ঝলক থাকলে সাধারণত দেখবেন থিমের script <body>-এর শেষে, বা একটি বড় JavaScript ফাইলের ভেতরে (অনেক সময় framework লোড হওয়ার পরে) — অর্থাৎ প্রথম আঁকার পরে চলছে। ঝলক না থাকলে <head>-এ CSS-এর আগে একটি ছোট inline script পাবেন, অথবা সার্ভার থেকেই <html data-theme="dark"> বা class="dark" লেখা অবস্থায় HTML আসছে (Network প্যানেলে মূল HTML-এর Response দেখলে বোঝা যায়)। এই পর্যবেক্ষণই অংশ ৬-এর ডায়াগ্রামের বাস্তব প্রমাণ।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমআগে থেকে “ডার্ক” বেছে রাখা একটি সাইট ঝলক ছাড়া খোলার সময় ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 📄 ব্রাউজার <meta name="color-scheme"> পড়ে
  2. 🧠 head-এর ছোট script localStorage থেকে পছন্দ পড়ে
  3. 🏷️ <html>-এ data-theme="dark" বসে
  4. 🎨 CSS এসে ডার্ক টোকেন মেলায়
  5. 🖥️ প্রথম আঁকাই গাঢ় রঙে হয়

CSS-এ :root { color-scheme: light dark; } লেখার সরাসরি ফল কী?

  • পাতার সব রং, নিজের দেওয়া রংসহ, স্বয়ংক্রিয়ভাবে উল্টে যায়
  • ব্রাউজার ডিফল্ট পটভূমি, ফর্ম কন্ট্রোল ও scrollbar ব্যবহারকারীর পছন্দের থিমে আঁকে
  • ব্রাউজার পাতায় একটি থিম বদলের বোতাম যোগ করে
  • ব্যবহারকারীর পছন্দ localStorage-এ সংরক্ষণ করে

ব্যবহারকারী সিস্টেমে ডার্ক থিম বেছে রেখেছেন কি না, CSS-এ কীভাবে জানা যায়?

  • @color-scheme dark { … }
  • @media (dark-mode: on) { … }
  • @media (prefers-color-scheme: dark) { … }
  • @media (theme-color: dark) { … }

একটি পাতায় body { background: light-dark(#ffffff, #18181b); } আছে, কিন্তু কোথাও color-scheme নেই। ডার্ক সিস্টেমে পটভূমি কী হবে?

  • #18181b, কারণ সিস্টেম ডার্ক
  • ঘোষণাটি অবৈধ ধরে বাদ পড়বে, পটভূমি স্বচ্ছ হবে
  • দুই রঙের মিশ্রণে একটি ধূসর
  • #ffffff, কারণ color-scheme ছাড়া used color scheme লাইট

থিম বদলের বোতামওয়ালা একটি সাইট reload করলে এক মুহূর্ত সাদা দেখিয়ে তারপর কালো হয়। সবচেয়ে সম্ভাব্য কারণ কী?

  • সংরক্ষিত পছন্দ পড়ার script প্রথম আঁকার পরে, যেমন body-র শেষে, চলছে
  • CSS ফাইলটি খুব বড়
  • localStorage প্রতিবার মুছে যায়
  • ব্যবহারকারীর সিস্টেম লাইট মোডে আছে

থিমযোগ্য design system-এ কার্ডের পটভূমির variable-এর জন্য কোন নামটি সবচেয়ে ভালো?

  • --white
  • --surface
  • --gray-100
  • --ffffff

একটি ডার্ক থিম নকশার সময় কোন সিদ্ধান্তটি সবচেয়ে ভালো?

  • পটভূমি #000, লেখা #fff — সর্বোচ্চ contrast-ই সেরা
  • লাইট থিমের ব্র্যান্ড রং হুবহু রেখে শুধু পটভূমি গাঢ় করা
  • গাঢ় ধূসর পটভূমি, কম তীব্র হালকা রং, ওপরের স্তর একটু হালকা, আর দুই থিমেই contrast মাপা
  • html { filter: invert(1); } দিয়ে পুরো পাতা উল্টে দেওয়া

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Dark mode একটি পছন্দ: সিস্টেমের থিম ডিফল্ট হিসেবে মানুন, বদলানোর সুযোগ দিন, দুই থিমেই সমান যত্ন নিন।
  2. color-scheme: light dark (আর একই meta) ব্রাউজারের ডিফল্ট রং, ফর্ম ও scrollbar বদলায়; prefers-color-scheme ব্যবহারকারীর পছন্দ জিজ্ঞেস করে।
  3. কম্পোনেন্টে শুধু semantic token (--surface, --text, --accent); থিম মানে টোকেনের মান-সেট — মৌসুমি থিমও তাই। light-dark() কাজ করে শুধু color-scheme থাকলে।
  4. নিজের বোতাম: <html data-theme> দিয়ে color-scheme বদল, localStorage-এ মনে রাখা, আর head-এ CSS-এর আগে ছোট script — নইলে ঝলক।
  5. ডার্ক প্যালেট: গাঢ় ধূসর, কম তীব্র রং, স্তরে স্তরে হালকা; দুই থিমে আলাদা contrast; ছবিতে <picture>, invert সাবধানে; forced colors-এ ব্যবহারকারীর রং জিতবে; DevTools-এর Rendering প্যানেলে পরীক্ষা।

পরের পর্বে: এবার একেবারে নতুন CSS-এর জগৎ — JavaScript ছাড়াই একটি element-কে আরেকটির পাশে আটকে রাখা (anchor positioning), টাইপ-সহ custom property (@property), নির্দিষ্ট এলাকায় সীমাবদ্ধ style (@scope), আর পাতা বদলের মসৃণ অ্যানিমেশন (View Transitions) — যার কিছু আজ থিম বদলের মুহূর্তটিকেও সুন্দর করতে পারে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান