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

অ্যাক্সেসিবল CSS — focus, contrast, reduced motion আর বড় লেখা

Accessible CSS — Contrast, Focus Indicators, Reduced Motion, Forced Colors, Zoom & Text Spacing

Taught in Bangla, this lesson shows which WCAG requirements CSS controls: colour contrast ratios and not using colour alone, clear focus indicators with :focus-visible and scroll-padding under sticky headers, prefers-reduced-motion, prefers-contrast and forced colors, rem units, zoom, reflow and text spacing, target size and hover-dependent design traps, hiding content and order, print, and testing with DevTools, Lighthouse and the keyboard.

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

  • WCAG-এর কোন কোন শর্ত সরাসরি CSS-এর হাতে, আর contrast ratio কীভাবে হিসাব হয়
  • চোখে পড়ার মতো focus নকশা — :focus-visible, outline বনাম box-shadow, sticky header-এর নিচে focus হারানো ঠেকানো
  • reduced motion, prefers-contrast আর forced colors (Windows-এর contrast theme)-এর জন্য CSS লেখার ধরন
  • rem, zoom, reflow আর text spacing — বড় লেখাতেও না ভাঙা লেআউট; চাপার জায়গার মাপ আর hover-নির্ভর নকশার ফাঁদ
  • লুকানো আর ক্রমের নিয়মের পুনরালোচনা, প্রিন্টে অ্যাক্সেসিবিলিটি, আর DevTools-Lighthouse-কি-বোর্ড দিয়ে পরীক্ষার পদ্ধতি

১ · অ্যাক্সেসিবিলিটির অর্ধেক কাজ CSS-এর

পর্ব ১২-এ (অ্যাক্সেসিবিলিটি — সবার জন্য ওয়েব, screen reader আর ARIA-র সঠিক ব্যবহার) আপনি দেখেছেন, সঠিক HTML দিলে screen reader বুঝতে পারে কোনটি বোতাম, কোনটি heading, কোন ঘরের নাম কী। সেসব এখানে আবার বলব না। এই পর্বের প্রশ্ন: সঠিক HTML-কে CSS দিয়ে সাজাতে গিয়ে আমরা কীভাবে তাকে অব্যবহারযোগ্য করে ফেলি — আর কীভাবে আরও বেশি মানুষের ব্যবহারযোগ্য করি।

একটি দৃশ্য ভাবুন। নাসরিন আপা ষাটোর্ধ্ব, চশমা পরেন, ফোনের লেখা বড় করে রাখেন। তিনি একটি অনলাইন দোকানে ওষুধ অর্ডার করছেন। দাম লেখা হালকা ধূসরে — পড়তে কষ্ট। “অর্ডার করুন” বোতামটি দেখতে সুন্দর, কিন্তু লেখা বড় করায় বোতামের লেখা কেটে গেছে। পাশে রঙিন ব্যানার একটানা নড়ছে, তাঁর মাথা ঝিমঝিম করছে। আর তাঁর ছেলে, যে কবজির ব্যথার জন্য মাউস নয় কি-বোর্ড ব্যবহার করে, Tab চেপে কোথায় আছে বুঝতেই পারছে না, কারণ ডিজাইনার focus-এর দাগ মুছে দিয়েছেন। HTML-এ একটিও ভুল নেই — সব ভুল CSS-এ।

বাড়ির কাঠামো আর রঙের কথা ভাবুন। HTML হলো কাঠামো — দরজা কোথায়, সিঁড়ি কোথায়, র‍্যাম্প আছে কি না। CSS হলো রং, আলো আর সাইনবোর্ড। সিঁড়ির ধাপ মেঝের রঙেই রাঙানো হলে দৃষ্টিশক্তি কম এমন কেউ হোঁচট খাবেন; “বের হওয়ার পথ” সাইনবোর্ড গাছের আড়ালে থাকলে না থাকারই সমান। অ্যাক্সেসিবল CSS মানে সেই রং, আলো আর সাইনবোর্ডের যত্ন।

পর্ব ১২-এ দেখা WCAG 2.2-এর বহু success criterion সরাসরি CSS-এর হাতে। নিচের টেবিলটি এই পর্বের মানচিত্র — প্রতিটি সারি সামনের কোনো না কোনো অংশে বিস্তারিত আসবে:

শর্ত (স্তর)এক কথায়CSS-এ কোথায় ভুল হয়
1.4.3 Contrast (Minimum) — AAলেখা পড়ার মতো গাঢ়হালকা ধূসর লেখা, ছবির ওপর লেখা
1.4.11 Non-text Contrast — AAবোতাম, ঘর, আইকন, focus-এর দাগ চোখে পড়েফিকে বর্ডার, হালকা আইকন
1.4.1 Use of Color — Aশুধু রঙে অর্থ নয়underline ছাড়া লিংক, শুধু লাল বর্ডারে ভুল
2.4.7 Focus Visible — AAকি-বোর্ডে focus দেখা যায়outline: none
2.4.11 Focus Not Obscured (Minimum) — AAfocus করা জিনিস পুরো ঢাকা পড়ে নাsticky header, cookie ব্যানার
2.3.3 Animation from Interactions — AAAনড়াচড়া বন্ধ করা যায়parallax, বড় স্লাইড অ্যানিমেশন
1.4.4 Resize Text — AA২০০% বড় করলেও সব কাজ করেস্থির উচ্চতা, overflow: hidden
1.4.10 Reflow — AA৩২০px চওড়ায় পাশাপাশি স্ক্রল নেইনির্দিষ্ট চওড়ার কলাম
1.4.12 Text Spacing — AAলাইন-অক্ষরের ফাঁক বাড়ালেও কিছু কাটে নাpx-এ স্থির বাক্স
1.4.13 Content on Hover or Focus — AAhover-এ আসা জিনিস সামলানো যায়মাউস সরালেই হারানো tooltip
2.5.8 Target Size (Minimum) — AAচাপার জায়গা অন্তত 24×24 CSS pxছোট আইকন, গায়ে গায়ে লিংক
1.3.2 Meaningful Sequence, 2.4.3 Focus Order — Aচোখের ক্রম আর পড়ার ক্রম মেলেorder, grid-এ জায়গা বদল
মূল ধারণা

CSS-এর accessibility-চিন্তা তিনটি প্রশ্নে ভাগ করা যায়: (১) দেখা যায় তো? — contrast, রঙের বাইরে সংকেত, focus. (২) ব্যবহারকারীর পছন্দ মানছি তো? — কম নড়াচড়া, বেশি contrast, নিজের রং, বড় লেখা। (৩) চাপ সইতে পারে তো? — zoom, ছোট পর্দা, বাড়তি ফাঁক, আঙুল।

২ · রঙের contrast — ৪.৫:১, ৩:১ আর শুধু রঙে অর্থ নয়

“হালকা ধূসর লেখা দেখতে মার্জিত” — এই ধারণা ওয়েবের সবচেয়ে প্রচলিত অ্যাক্সেসিবিলিটি ভুলের জন্ম দেয়। Contrast ratio হলো লেখা আর পটভূমির আপেক্ষিক উজ্জ্বলতার অনুপাত — ১:১ (একই রং, অদৃশ্য) থেকে ২১:১ (কালোয় সাদা) পর্যন্ত। পর্ব ২০-এ এর পরিচয় পেয়েছেন; এখানে দেখব WCAG ঠিক কী চায়:

কীAA (সাধারণ লক্ষ্য)AAA
সাধারণ লেখা৪.৫:১৭:১ (1.4.6)
বড় লেখা — অন্তত 24px, অথবা অন্তত প্রায় 18.66px বোল্ড৩:১৪.৫:১
UI-এর অংশ ও গ্রাফিক্স — ঘরের সীমানা, চেকবক্স, আইকন, চার্টের রেখা, focus-এর দাগ (1.4.11)৩:১ পাশের রঙের সাথে—
লোগো, নিষ্ক্রিয় (disabled) কন্ট্রোল, নিছক সাজশর্ত নেইশর্ত নেই

বড় লেখার সীমা WCAG-এ লেখা আছে point-এ — ১৮pt আর ১৪pt বোল্ড, যা CSS-এ মোটামুটি 24px আর 18.66px. নিচের এডিটরে একই সাদা পটভূমিতে কয়েকটি ধূসর আর একটি ইনপুটের সীমানা আছে; প্রতিটির পাশে মাপা ratio লেখা:

body { font-family: sans-serif; background: #ffffff; }
.a   { color: #959595; }   /* সাধারণ লেখায় ফেল */
.b   { color: #767676; }   /* AA-র একেবারে সীমায় */
.c   { color: #595959; }   /* AAA পাস */
.big { color: #949494; font-size: 24px; }  /* বড় লেখা: ৩:১ যথেষ্ট */
input { font: inherit; padding: 6px 8px; border-radius: 6px; width: 16em; }
.f1  { border: 1px solid #d4d4d8; }  /* ঘরের সীমানা প্রায় অদৃশ্য */
.f2  { border: 1px solid #8a8a8a; }  /* ৩:১ পার — ঘরটি চেনা যায় */

ওপরের লাইনটি সবচেয়ে ফিকে — রোদে ফোনে কষ্টকর; দ্বিতীয়টি AA-র ন্যূনতম; তৃতীয়টি আরামদায়ক। বড় শিরোনাম প্রায় প্রথম লাইনের মতোই ফিকে, কিন্তু বড় বলে ৩:১ যথেষ্ট। প্রথম ঘরটির সীমানা প্রায় হারিয়ে গেছে। চেষ্টা করুন: (১) .big-এর font-size 16px করুন — একই রং এখন সাধারণ লেখা, তাই শর্ত পূরণ করে না। (২) body-র পটভূমি #f4f4f5 করুন — হালকা ধূসর পটভূমিতে #767676-ও নেমে যায় প্রায় ৪.১:১-এ; পটভূমি বদলালে হিসাবও বদলায়।

ratio আসে কোথা থেকে — ধাপে ধাপে

চোখ সব রঙের উজ্জ্বলতা সমানভাবে টের পায় না — সবুজ আমাদের কাছে সবচেয়ে উজ্জ্বল, নীল সবচেয়ে কম। তাই WCAG প্রথমে প্রতিটি রঙের relative luminance বের করে, তারপর দুটি মিলিয়ে ratio. #767676 সাদায় কেন ঠিক ৪.৫৪:১, দেখুন:

  1. hex#767676তিনটি সংখ্যা
  2. ০ থেকে ১÷ 255মাপ সমান করা
  3. lineargamma সরানোআসল আলো
  4. luminanceLচোখের ওজনে
  5. ratio4.54:1রায়
  1. hex থেকে সংখ্যা। #767676-এর তিন জোড়া অক্ষর হলো লাল, সবুজ, নীল — প্রতিটি 76, দশমিকে ১১৮। সাদা মানে তিনটিই ২৫৫.
  2. ০ থেকে ১-এর মাপে আনা। প্রতিটি সংখ্যাকে ২৫৫ দিয়ে ভাগ। ১১৮ হলো ০.৪৬২৭ — কিন্তু এটি এখনো আসল আলোর পরিমাণ নয়।
  3. gamma সরানো। পর্দার রং একটি বাঁকা নিয়মে (sRGB gamma) সংরক্ষিত থাকে, যাতে গাঢ় রংগুলোতে বেশি সূক্ষ্মতা থাকে। সূত্রটি উল্টে দিলে আসল আলো পাই — ০.৪৬২৭ হয়ে যায় মাত্র ০.১৮১২। অর্থাৎ “মাঝারি ধূসর” আসলে সাদার পাঁচ ভাগের এক ভাগেরও কম আলো দেয়।
  4. চোখের ওজনে যোগ। সবুজের ওজন সবচেয়ে বেশি (০.৭১৫২), নীলের সবচেয়ে কম (০.০৭২২)। ধূসরে তিনটি সমান, তাই L = ০.১৮১২। এজন্যই হলুদ সাদার ওপর প্রায় অদৃশ্য, নীল স্পষ্ট।
  5. অনুপাত আর রায়। উজ্জ্বলটির L-এর সাথে ০.০৫ যোগ করে গাঢ়টির (L + ০.০৫) দিয়ে ভাগ। ০.০৫ যোগ করা হয় পর্দার চারপাশের আলোর প্রতিফলন ধরে নিতে, আর শূন্য দিয়ে ভাগ ঠেকাতে। ফল ৪.৫৪ — সাদায় সাধারণ লেখার সবচেয়ে হালকা গ্রহণযোগ্য ধূসরের কাছাকাছি।

এই সূত্র একবার বুঝলে নিজেই একটি ছোট contrast checker লেখা যায়। নিচের কোডটি কয়েক জোড়া রং মেপে বলে দেয় কোনটি কোন কাজে চলবে — লেখা, বড় লেখা, না শুধু UI-এর সীমানা:

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 = [
  ['ধূসর লেখা',            '#767676', '#ffffff'],
  ['সবুজ বোতামে সাদা লেখা', '#ffffff', '#16a34a'],
  ['ফিকে ঘরের সীমানা',      '#d4d4d8', '#ffffff'],
  ['নীল লিংক বনাম গাঢ় লেখা', '#1d4ed8', '#1f2937'],
  ['গাঢ় পটভূমিতে নীল focus', '#1d4ed8', '#111827'],
];
for (const [label, fg, bg] of pairs) {
  const r = ratio(fg, bg);
  const v = r >= 4.5 ? 'সব লেখায় AA' : r >= 3 ? 'শুধু বড় লেখা/UI' : 'ফেল';
  console.log(label + ': ' + r.toFixed(2) + ':1 → ' + v);
}

ফলাফলে দেখবেন: ধূসর লেখা ৪.৫৪:১ (পাস); জনপ্রিয় উজ্জ্বল সবুজ বোতামে সাদা লেখা মাত্র ৩.৩:১ — বোতামের ছোট লেখার জন্য যথেষ্ট নয়; ফিকে সীমানা ১.৪৮:১ — ফেল; নীল লিংক আর পাশের গাঢ় লেখার পার্থক্য ২.১৯:১ — অর্থাৎ শুধু রং দিয়ে লিংক চেনানো যাবে না (পরের অংশে কারণ); আর গাঢ় পটভূমিতে নীল focus-এর দাগ ২.৬৫:১ — ফেল। চেষ্টা করুন: সবুজটিকে #15803d করুন — ফল ৫.০২:১, পাস। দৈনন্দিন কাজে কোড লাগে না: DevTools-এ কোনো রঙের ছোট বর্গে ক্লিক করলে color picker নিজেই ratio আর AA/AAA দেখায়।

আরও গভীরে: WCAG 2-এর মূল লেখায় gamma-র সীমা ছিল ০.০৩৯২৮, যা sRGB-র একটি পুরোনো খসড়া থেকে আসা; বর্তমান sRGB মানে সংখ্যাটি ০.০৪০৪৫ — ৮-বিট রঙে ফলের কার্যত কোনো পার্থক্য নেই, তাই কোডে আধুনিক মানটি নেওয়া হয়েছে। সূত্রটি লেখার আকার-ওজন আর “কোনটি সামনে” আলাদা করে না, তাই কমলা পটভূমির মতো ক্ষেত্রে এর রায় চোখের অভিজ্ঞতার সাথে সবসময় মেলে না। WCAG 3-এর খসড়ায় APCA নামে নতুন পদ্ধতি আলোচিত হয়েছে, কিন্তু সেটি এখনো চূড়ান্ত মানদণ্ড নয়; আইন আর অডিটে WCAG 2.x-এর ratio-ই চলে।

শুধু রঙে অর্থ নয় — 1.4.1

প্রতি বারোজন পুরুষের মধ্যে মোটামুটি একজনের কোনো না কোনো ধরনের বর্ণান্ধতা (color vision deficiency) আছে — সবচেয়ে সাধারণ লাল-সবুজ গুলিয়ে ফেলা। তাই WCAG-এর use of color শর্ত বলে: রং যেন একমাত্র সংকেত না হয়। রং থাকুক, কিন্তু সাথে আকার, লেখা, আইকন বা দাগ:

<style>
  body { font-family: sans-serif; color: #1f2937; }
  a { color: #1d4ed8; }
  .bad a  { text-decoration: none; }
  .good a { text-underline-offset: 3px; }
  input { font: inherit; padding: 6px; border: 2px solid #8a8a8a; border-radius: 6px; }
  .bad input  { border-color: #dc2626; }
  .good input { border-color: #b91c1c; border-width: 3px; }
  .msg { color: #b91c1c; margin: 4px 0 0; }
</style>
<div class="bad">
  <p>ফেরতের নিয়ম জানতে <a href="#rules">এখানে দেখুন</a>, তারপর অর্ডার দিন।</p>
  <label>মোবাইল নম্বর <input value="01712"></label>
</div>
<hr>
<div class="good">
  <p>ফেরতের নিয়ম জানতে <a href="#rules">এখানে দেখুন</a>, তারপর অর্ডার দিন।</p>
  <label>মোবাইল নম্বর <input value="01712" aria-invalid="true" aria-describedby="m"></label>
  <p class="msg" id="m">⚠ ভুল: ১১ অঙ্কের নম্বর দিন</p>
</div>

ওপরের অংশে লিংকটি শুধু নীল রঙে আলাদা, আর ভুল ঘরটি শুধু লাল বর্ডারে; নিচের অংশে লিংকে underline, আর ভুলের সাথে আইকন ও লেখা। এবার বর্ণান্ধ চোখের একটি মোটা অনুকরণ করুন: <style>-এর শুরুতে html { filter: grayscale(1); } যোগ করুন। ওপরের লিংক আর লাল ঘর প্রায় চেনাই যায় না; নিচেরটি সাদা-কালোতেও পুরো অর্থ বহন করে। চেষ্টা করুন: .good a-তে text-decoration-thickness: 2px; দিয়ে দেখুন — underline-কে সুন্দর করা যায়, মুছে ফেলার দরকার নেই।

WCAG-এর একটি স্বীকৃত বিকল্পও আছে: লিংকের রং পাশের লেখার সাথে অন্তত ৩:১ পার্থক্যে থাকলে, আর hover ও focus-এ বাড়তি সংকেত দেখালে, underline ছাড়াও চলে। কিন্তু checker-এ দেখেছেন, সাধারণ নীল আর গাঢ় লেখার পার্থক্য মাত্র ২.১৯:১। তাই লেখার ভেতরের লিংকে underline রাখাই নিরাপদ; মেনুর মতো জায়গায়, যেখানে অবস্থান দেখেই লিংক চেনা যায়, সেখানে না থাকলেও চলে।

৩ · Focus — কি-বোর্ড ব্যবহারকারীর কার্সর

মাউস ব্যবহারকারী দেখেন তীর কোথায়। কি-বোর্ড ব্যবহারকারীর কাছে সেই তীরের কাজ করে focus-এর দাগ — কোন লিংক বা বোতাম এই মুহূর্তে Enter চাপলে কাজ করবে। পর্ব ১২-এ দেখেছেন Tab কোন ক্রমে চলে; এখানে প্রশ্ন হলো সেই অবস্থানটি দেখা যায় কি না। WCAG 2.4.7 (AA) এটুকুই চায়: কি-বোর্ডে চালানো যায় এমন প্রতিটি জিনিসে focus দৃশ্যমান হবে।

ওয়েবের সবচেয়ে ক্ষতিকর এক লাইন: *:focus { outline: none; }. ডিজাইনার মাউসে ক্লিক করে বোতামের চারপাশে নীল দাগ দেখে বিরক্ত হয়ে এটি লেখেন — আর পুরো সাইট কি-বোর্ড ব্যবহারকারীর জন্য অন্ধকার ঘর হয়ে যায়। দাগ পছন্দ না হলে মুছবেন না, বদলাবেন।

সেই বিরক্তির সমাধান হলো পর্ব ৩৪-এ দেখা :focus-visible. :focus সত্য হয় যেকোনোভাবে focus এলে; :focus-visible সত্য হয় শুধু তখন, যখন ব্রাউজারের মনে হয় ব্যবহারকারীর দাগটি দেখা দরকার। ব্রাউজার কীভাবে সেই সিদ্ধান্ত নেয়, ধাপে ধাপে দেখুন:

  1. মাউসে বোতামclickদাগ নয়
  2. Tab চাপাTabদাগ চাই
  3. লেখার ঘরinputসবসময় দাগ
  4. scriptel.focus()আগের অবস্থা দেখে
  1. মাউসে বোতাম ক্লিক। তীরটিই তো সেখানে। তাই ব্রাউজার :focus-visible মেলায় না, দাগ দেখায় না।
  2. Tab চাপা। কি-বোর্ড থেকে focus সরেছে, ব্যবহারকারীর চোখে কোনো তীর নেই। ব্রাউজার :focus-visible মেলায়।
  3. লেখার ঘরে ক্লিক। টাইপ করার ঘরে focus এলে ব্যবহারকারীর জানা দরকার কোথায় লিখছেন, যেভাবেই আসুক। তাই input বা textarea-তে মাউসে ক্লিক করলেও :focus-visible সত্য।
  4. script দিয়ে focus. el.focus() হলে ব্রাউজার দেখে আগের focus কীভাবে এসেছিল: কি-বোর্ড থেকে এলে দাগ দেখায়, মাউস থেকে এলে সাধারণত দেখায় না। নিয়মগুলো স্পেসিফিকেশনে “পরামর্শ” আকারে, তাই ব্রাউজারভেদে সামান্য পার্থক্য থাকে। :focus-visible এখন Baseline Widely available.

ভালো focus-এর নকশা — outline, outline-offset আর দুই রঙের দাগ

focus-এর দাগের জন্য সবচেয়ে নির্ভরযোগ্য হাতিয়ার outline: এটি জায়গা নেয় না, তাই লেআউট নড়ে না; আর আধুনিক ব্রাউজারে এটি border-radius-এর বাঁক মেনে চলে। outline-offset দাগটিকে বাক্স থেকে একটু দূরে সরায়, ফলে বোতামের নিজের রঙের সাথে মিশে যায় না। নিচের প্রিভিউর সাদা অংশে একবার ক্লিক করুন, তারপর Tab চেপে চেপে এগোন:

body { font-family: sans-serif; }
button { font: inherit; padding: 8px 14px; margin: 4px;
         border: 1px solid #8a8a8a; border-radius: 8px; background: #f8fafc; }
.dark { background: #111827; padding: 12px; border-radius: 8px; }

.b2:focus { outline: none; }          /* ভুল — দাগ উধাও */

.b3:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }

/* ভেতরে গাঢ় outline, বাইরে সাদা ছায়া — যেকোনো পটভূমিতে চোখে পড়ে */
.b4:focus-visible { outline: 3px solid #111827; outline-offset: 2px;
                    box-shadow: 0 0 0 7px #ffffff; }

Tab চাপলে “হোম” আর “ডিফল্ট”-এ ব্রাউজারের নিজের দাগ, “outline: none”-এ কিছুই নেই — focus কোথায় গেল বোঝার উপায় নেই। “নিজের দাগ” পরিষ্কার, বোতাম থেকে একটু দূরে। গাঢ় অংশে নীল দাগ প্রায় হারিয়ে যায় (২.৬৫:১), কিন্তু দুই রঙের দাগ দুই পটভূমিতেই চোখে পড়ে। এবার মাউসে বোতামগুলোতে ক্লিক করুন — :focus-visible থাকায় কোনো দাগ আসে না। চেষ্টা করুন: .b3-এর :focus-visible-কে :focus করুন, তারপর মাউসে ক্লিক করুন — এবার ক্লিকেও দাগ।

শুধু box-shadow দিয়ে দাগ আঁকলে কী সমস্যা? অংশ ৫-এ দেখবেন, Windows-এর contrast theme চালু থাকলে ব্রাউজার সব box-shadow মুছে দেয় — ছায়ায় আঁকা focus তখন অদৃশ্য। নিয়ম তাই: মূল দাগ outline-এ, ছায়া শুধু বাড়তি সাজ। একান্তই ছায়া দিতে চাইলে সাথে outline: 2px solid transparent রাখুন — সাধারণ অবস্থায় স্বচ্ছ, কিন্তু forced colors-এ ব্রাউজার একে দৃশ্যমান রং দেয়।

WCAG 2.2-এ AAA স্তরে একটি নতুন শর্ত এসেছে — 2.4.13 Focus Appearance. মোটামুটি বললে: দাগের ক্ষেত্রফল অন্তত বাক্সের চারপাশে 2 CSS px পুরু একটি রেখার সমান, আর focus থাকা ও না-থাকা অবস্থার রঙে অন্তত ৩:১ পার্থক্য। AAA বাধ্যতামূলক না হলেও ভালো মাপকাঠি — অন্তত 2px পুরু, স্পষ্ট রঙের outline দিলে সাধারণত সহজেই মেলে।

sticky header-এর নিচে হারানো focus — 2.4.11

পর্ব ২৪-এর sticky header কাজের, কিন্তু একটি ফাঁদও। Shift+Tab চেপে ওপরে উঠলে ব্রাউজার focus পাওয়া লিংকটিকে স্ক্রল করে একেবারে ওপরের কিনারায় আনে — ঠিক যেখানে header বসে আছে। লিংক focus পেল, কিন্তু চোখে পড়ল না। WCAG 2.2-এর 2.4.11 Focus Not Obscured (Minimum, AA) বলে, focus পাওয়া জিনিস লেখকের বানানো কোনো কিছুর নিচে পুরোপুরি ঢাকা পড়বে না (AAA স্তরের 2.4.12 বলে, একটুও না)। নিচে একটি ছোট “পাতা” আছে, যার নিজের স্ক্রল আছে:

<style>
  .page { height: 210px; overflow: auto; border: 1px solid #8a8a8a;
          font-family: sans-serif; scroll-padding-top: 0; }
  .page header { position: sticky; top: 0; padding: 12px;
                 background: #0f766e; color: #fff; }
  .page header a { color: #fff; }
  .page p { margin: 0; padding: 10px 12px; border-bottom: 1px solid #e5e7eb; }
  .page a:focus-visible, .page p:target { outline: 3px solid #b45309; outline-offset: -3px; }
</style>
<div class="page">
  <header>রহিম স্টোর · <a href="#p7">৭ নম্বর পণ্যে যান</a></header>
  <p id="p1"><a href="#p1">১ · মিনিকেট চাল</a></p>  <p id="p2"><a href="#p2">২ · মসুর ডাল</a></p>
  <p id="p3"><a href="#p3">৩ · সয়াবিন তেল</a></p>   <p id="p4"><a href="#p4">৪ · আটা</a></p>
  <p id="p5"><a href="#p5">৫ · চিনি</a></p>          <p id="p6"><a href="#p6">৬ · লবণ</a></p>
  <p id="p7"><a href="#p7">৭ · খাঁটি মধু</a></p>     <p id="p8"><a href="#p8">৮ · গুঁড়া দুধ</a></p>
  <p id="p9"><a href="#p9">৯ · চা-পাতা</a></p>      <p id="p10"><a href="#p10">১০ · সুজি</a></p>
</div>

header-এর “৭ নম্বর পণ্যে যান” লিংকে ক্লিক করুন: বাক্সটি স্ক্রল করে ৭ নম্বরকে একেবারে ওপরে আনে — সবুজ header-এর নিচে, তাই সেটি দেখাই যায় না। Tab চেপে নিচ পর্যন্ত গিয়ে Shift+Tab চেপে ওপরে উঠলেও একই ঘটনা: focus পাওয়া লিংক header-এর নিচে লুকায়। সমাধান এক লাইনের: scroll-padding-top: 0-কে scroll-padding-top: 52px করুন, আবার চেষ্টা করুন — এখন ব্রাউজার header-এর উচ্চতা বাদ দিয়ে হিসাব করে, ৭ নম্বর header-এর ঠিক নিচে দৃশ্যমান থাকে।

আসল পাতায় এই scroll-padding বসে html-এ: html { scroll-padding-top: 4rem; } — header-এর উচ্চতা জানা থাকলে সেটুকু, অথবা একটি CSS variable থেকে। WCAG-এর নিজস্ব কৌশল-তালিকাতেও (Technique C43) ঠিক এই পদ্ধতি আছে। cookie ব্যানার, নিচে আটকে থাকা “চ্যাট করুন” বোতাম বা মোবাইলের নিচের মেনুর জন্য একইভাবে scroll-padding-bottom. আর ব্যানার যদি অনেক জায়গা নেয়, ব্যবহারকারীকে বন্ধ করার সুযোগ দিন।

৪ · নড়াচড়া — prefers-reduced-motion আর “আগে স্থির” নীতি

পর্ব ৩৮–৪২-এ transition, animation, transform আর স্ক্রল ইফেক্ট শিখেছেন। এগুলো পাতাকে প্রাণবন্ত করে — কিন্তু সবার জন্য নয়। ভেতরের কানের ভারসাম্য-ব্যবস্থার সমস্যা, অর্থাৎ vestibular disorder থাকলে, পর্দার বড় নড়াচড়া — বিশেষ করে জুম, ঘূর্ণন আর parallax (স্ক্রলের সময় পেছনের স্তর আলাদা গতিতে সরা) — মাথা ঘোরা, বমিভাব, মাইগ্রেন এমনকি কয়েক ঘণ্টার অসুস্থতা আনতে পারে। মনোযোগের সমস্যায় একটানা নড়া ব্যানার পড়াই কঠিন করে।

ব্যবহারকারীরা অপারেটিং সিস্টেমে এ জন্য একটি সেটিং চালু রাখেন — Windows-এ “Animation effects” বন্ধ, macOS ও iOS-এ “Reduce motion”, Android-এ “Remove animations”. ব্রাউজার সেটি prefers-reduced-motion media query দিয়ে CSS-কে জানায়; মান reduce অথবা no-preference. এটি Baseline Widely available. CSS লেখার দুটি দর্শন আছে:

কমানোর পথ

সাধারণভাবে সব অ্যানিমেশন লিখুন, তারপর @media (prefers-reduced-motion: reduce)-এ বন্ধ বা নরম করুন। ঝুঁকি: নতুন অ্যানিমেশনের reduce অংশ লিখতে ভুলে গেলে সেটি সবাইকে দেখাবে।

পুরোনো কোডে দ্রুত সমাধান

আগে স্থির (no-motion-first)

স্বাভাবিক CSS-এ কোনো বড় নড়াচড়া নেই; অ্যানিমেশন লেখা হয় শুধু @media (prefers-reduced-motion: no-preference)-এর ভেতরে। ভুলে গেলে শুধু অ্যানিমেশন দেখা যায় না — কারও শরীর খারাপ হয় না।

নতুন প্রজেক্টে নিরাপদ ডিফল্ট
body { font-family: sans-serif; }
.banner { padding: 10px; background: #fef3c7; border-radius: 8px; }
.card { margin: 12px 0; padding: 10px; border: 1px solid #8a8a8a; border-radius: 8px; }
button { font: inherit; padding: 8px 14px; border-radius: 8px; }

/* আগে স্থির: নড়াচড়া শুধু যাঁরা আপত্তি জানাননি তাঁদের জন্য */
@media (prefers-reduced-motion: no-preference) {
  .banner { animation: slide 1s ease-in-out 4 alternate; }
  .card   { animation: pop .7s ease-out both; }
  button:hover { transform: scale(1.06); transition: transform .2s; }
}
/* কম নড়াচড়া চাইলেও অর্থ থাকুক — জায়গা বদল নয়, শান্ত fade */
@media (prefers-reduced-motion: reduce) {
  .card { animation: fade .7s ease-out both; }
}
@keyframes slide { to { transform: translateX(40px); } }
@keyframes pop   { from { transform: scale(.5) translateY(40px); opacity: 0; } }
@keyframes fade  { from { opacity: 0; } }

আপনার সিস্টেমে “reduce motion” বন্ধ থাকলে ব্যানারটি চারবার দোলে, কার্ডটি নিচ থেকে লাফিয়ে আসে, আর বোতামে মাউস রাখলে বড় হয়। চালু থাকলে ব্যানার স্থির, বোতাম স্থির, কার্ড শুধু ধীরে ফুটে ওঠে। সেটিং না বদলেই অন্য রূপটি দেখতে প্রথম media query-র no-preference-কে reduce আর দ্বিতীয়টির reduce-কে no-preference করুন (শুধু পরীক্ষার কৌশল)। চেষ্টা করুন: ব্যানারের 4-কে infinite করুন — পাঁচ সেকেন্ডের বেশি একটানা নড়া জিনিসে WCAG 2.2.2 (A) অনুযায়ী থামানোর উপায় দিতেই হবে, reduce motion থাকুক বা না থাকুক।

“reduce” মানে “remove” নয়। অ্যানিমেশন প্রায়ই অর্থ বহন করে, তাই কম মোশন চাইলে জায়গা বদল, জুম, ঘূর্ণন আর parallax বাদ দিন, কিন্তু শান্ত fade বা ছোট রং-বদল রাখতে পারেন। অনেক reset-এ একটি “সব বন্ধ” নিয়ম দেখবেন:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

এটি জরুরি অবস্থার জাল হিসেবে কাজের — 0-এর বদলে 0.01ms রাখা হয় যাতে animationend-এর মতো ঘটনার ওপর নির্ভর করা JavaScript ভেঙে না যায়। কিন্তু এটি fade-এর মতো উপকারী সংকেতও মুছে দেয়, আর পর্ব ৪২-এর scroll-driven animation-এ ফল অপ্রত্যাশিত হতে পারে। তাই নতুন কাজে “আগে স্থির” নীতিই ভালো। আর scroll-behavior: smooth-ও media query-র ভেতরে রাখুন — দীর্ঘ মসৃণ স্ক্রল নিজেও একটি বড় নড়াচড়া।

২০১৩ সালে iOS 7-এ হোম স্ক্রিনের parallax আর জুম-অ্যানিমেশন আসার পর অনেক ব্যবহারকারী মাথা ঘোরা ও বমিভাবের অভিযোগ করেন; Apple এরপর “Reduce Motion” সেটিং যোগ করে। পরে ওয়েবেও সেই পছন্দ পৌঁছায় — ২০১৭ সালে WebKit (Safari) প্রথম prefers-reduced-motion চালু করে, অন্য ব্রাউজারগুলো পরের কয়েক বছরে।

৫ · prefers-contrast আর forced colors — ব্যবহারকারীর রং

কেউ কেউ শুধু বেশি contrast চান — ফিকে সীমানা আর হালকা ধূসর তাঁদের কাছে অদৃশ্য। macOS ও iOS-এ “Increase contrast”-এর মতো সেটিং চালু থাকলে prefers-contrast media query more হয় (মানগুলো no-preference, more, less, custom). এখানে আপনার কাজ সহজ: নিজের নকশাকে আরও স্পষ্ট করা।

@media (prefers-contrast: more) {
  :root { --text-muted: #374151; --border: #4b5563; }   /* ধূসর আরও গাঢ় */
  .card { border-width: 2px; }
  .glass { backdrop-filter: none; background: Canvas; }  /* ঝাপসা কাচ বাদ */
}

অন্যরা আরও এক ধাপ এগিয়ে নিজের রং-ই চাপিয়ে দেন। Windows-এ “Contrast themes” (আগের নাম High Contrast) চালু করলে ব্রাউজার forced colors mode-এ যায়, আর @media (forced-colors: active) সত্য হয়। কম দৃষ্টিশক্তি বা আলোর প্রতি তীব্র সংবেদনশীলতার কারণে অনেকে এটি ব্যবহার করেন। তখন ব্রাউজার কী করে:

  • color, background-color, border-color, outline-color, SVG-র fill/stroke-এর মতো রঙের property-তে আপনার রং বাদ দিয়ে ব্যবহারকারীর থিমের অল্প কয়েকটি রং বসায়।
  • box-shadow আর text-shadow পুরো মুছে দেয়; gradient-এর মতো তৈরি করা background-image-ও বাদ পড়ে (url() দিয়ে আনা আসল ছবি থাকে)।
  • শুধু পটভূমির রঙে আলাদা করা বোতাম বা ছায়ায় আঁকা focus একাকার হয়ে যায়।

এই রংগুলো আসে নাম দেওয়া system color থেকে, যা পর্ব ৩৬-এ color-scheme-এর প্রসঙ্গে দেখেছেন। forced colors-এ এদের মান ব্যবহারকারীর থিম ঠিক করে। নিচের এডিটরে প্রধান system color-গুলো সাজানো আছে, আর দুটি বোতাম — একটির সীমানা ছায়ায়, অন্যটির border-এ:

:root { color-scheme: light; }        /* dark করে দেখুন */
body  { font-family: sans-serif; }
.sw { padding: 6px 10px; margin: 4px 0; border: 1px solid CanvasText; }
.c1 { background: Canvas;     color: CanvasText; }
.c2 { background: Canvas;     color: LinkText; }
.c3 { background: ButtonFace; color: ButtonText; border-color: ButtonBorder; }
.c4 { background: Field;      color: FieldText; }
.c5 { background: Highlight;  color: HighlightText; }
.c6 { background: Canvas;     color: GrayText; }
button { font: inherit; padding: 8px 14px; border-radius: 8px; background: #dbeafe; }
.shadow { border: 0; box-shadow: 0 0 0 2px #2563eb; }      /* forced colors-এ উধাও */
.real   { border: 2px solid #2563eb; }                     /* forced colors-এ থাকে */

@media (forced-colors: active) {
  .shadow { border: 2px solid ButtonBorder; }   /* সাহায্য: আসল সীমানা যোগ */
}

প্রতিটি সারি ব্রাউজারের বর্তমান থিমের system color দেখাচ্ছে; বোতাম দুটি এখন প্রায় একই রকম। চেষ্টা করুন: (১) light-কে dark করুন — একই নামগুলো গাঢ় থিমের মান নিল; forced colors-এ ঠিক এভাবে ব্যবহারকারীর থিমের মান আসে। (২) এই পাতাতেই DevTools খুলে Rendering প্যানেলে Emulate CSS media feature forced-colors থেকে active বাছুন (অংশ ৯-এ পদ্ধতি) — প্রিভিউর সব রং বদলে যাবে, আর শেষ media query না থাকলে “ছায়ায় সীমানা” বোতামটি সীমানাহীন এক টুকরো লেখা হয়ে যেত। শেষ নিয়মটি মুছে আবার দেখুন।

forced colors-এ ভালো নাগরিক হওয়ার কয়েকটি নিয়ম:

  1. সীমানা border বা outline-এ রাখুন, শুধু পটভূমির রঙে বা ছায়ায় নয়। নকশায় border না চাইলে border: 1px solid transparent দিন — সাধারণ অবস্থায় অদৃশ্য, forced colors-এ দৃশ্যমান।
  2. রং দরকার হলে system color — CanvasText, LinkText, Highlight, ButtonBorder, GrayText. নিজের hex নয়।
  3. SVG আইকনে currentColor (পর্ব ৪৪) — তাহলে আইকন লেখার রং মেনে নিজেই বদলায়।
  4. forced-color-adjust: none শুধু বিরল ক্ষেত্রে — যেমন রঙের নমুনা দেখানোর ঘর (কাপড়ের দোকানে “লাল শাড়ি”-র রঙের বৃত্ত), যেখানে রংটাই তথ্য।

forced-colors media query প্রধান ব্রাউজারগুলো চেনে; বাস্তবে forced colors মোড চালু হয় মূলত Windows-এ, Chromium-ভিত্তিক ব্রাউজার আর Firefox-এ। পুরোনো -ms-high-contrast media query আজ শুধু ইতিহাস (প্র ০৪ দেখুন)।

৬ · বড় লেখা — rem, zoom, reflow আর text spacing

বয়স চল্লিশ পেরোলে প্রায় সবারই কাছের লেখা পড়তে কষ্ট হতে থাকে। তাই বিপুল সংখ্যক মানুষ লেখা বড় করে ওয়েব দেখেন — দুইভাবে: পুরো পাতা zoom (Ctrl++, যা সব কিছু বড় করে) আর ব্রাউজারের ডিফল্ট font size বদল (Chrome-এ Settings → Appearance → Font size, যা শুধু লেখার মূল মাপ বদলায়)।

ব্রাউজারের zoom আসলে একটি CSS px-কে বেশি ডিভাইস pixel দিয়ে আঁকে। ফল হলো, পাতার কাছে জানালা “ছোট” হয়ে যায় — আর আপনার media query সেই অনুযায়ী সাড়া দেয়। ধাপে ধাপে দেখুন:

  1. ১০০%1280pxতিন কলাম
  2. ২০০%640pxএক কলাম
  3. ৪০০%320pxফোনের মতো
  4. font size1rem = 24pxশুধু লেখা
  1. স্বাভাবিক অবস্থা। ১২৮০ পিক্সেল চওড়া জানালা, zoom ১০০% — viewport ১২৮০ CSS px, তিন কলামের নকশা।
  2. ২০০% zoom. প্রতিটি CSS px দুটি ডিভাইস pixel জুড়ে আঁকা হয়, তাই জানালায় ধরে মাত্র ৬৪০ CSS px — media query এক কলামে নামিয়ে দেয়। WCAG 1.4.4 (Resize Text, AA) চায় ২০০% পর্যন্ত বড় করলেও কোনো লেখা বা কাজ না হারায়।
  3. ৪০০% zoom. ১২৮০ ÷ ৪ = ৩২০ CSS px — ঠিক একটি ছোট ফোনের মাপ। Reflow (1.4.10, AA) বলে, এই চওড়ায় সাধারণ লেখা পড়তে পাশাপাশি (horizontal) স্ক্রল লাগবে না; শুধু মানচিত্র, বড় ডেটা টেবিল বা ছবি-সম্পাদকের মতো দুই দিকের জিনিসে ছাড় আছে।
  4. শুধু font size বদল। zoom নয়, শুধু ডিফল্ট লেখার মাপ ১৬ থেকে ২৪ করা হয়েছে। এখন rem ও em-এ লেখা সব বাড়ে; px-এ মাপ দেওয়া লেখা একটুও বাড়ে না — ব্যবহারকারীর পছন্দ উপেক্ষিত। root-এ html { font-size: 16px; } লিখলেও একই ক্ষতি। আর media query em-এ লিখলে (যেমন 48em) সেটিও এই পছন্দ বুঝে আগেই এক কলামে নামে।
মূল ধারণা

লেখার মাপ rem-এ, ফাঁক ও প্যাডিং প্রায়ই rem বা em-এ, html-এর font-size হয় কিছুই না, নয় শতাংশে। উচ্চতা কখনো লেখার পাত্রে স্থির নয় — height-এর বদলে min-height, আর লেখার ঘরে overflow: hidden নয়। border-এর মতো সরু রেখায় px চলবে — সেটি বড় না হলেও কারও ক্ষতি নেই।

Text spacing — 1.4.12-এর চাপ পরীক্ষা

ডিসলেক্সিয়া বা কম দৃষ্টিশক্তির অনেকে extension দিয়ে লাইন, অক্ষর আর শব্দের ফাঁক বাড়িয়ে পড়েন। WCAG 1.4.12 Text Spacing (AA) চায়, ব্যবহারকারী নিচের চারটি মান দিলেও কোনো লেখা কেটে, ঢেকে বা হারিয়ে যাবে না: line-height লেখার মাপের ১.৫ গুণ, অনুচ্ছেদের পরে ফাঁক ২ গুণ, letter-spacing ০.১২ গুণ, word-spacing ০.১৬ গুণ। এই মানগুলো বসানোর জনপ্রিয় টুল হলো Steve Faulkner-এর “text spacing bookmarklet”; নিচের এডিটরের শেষ চার লাইন সেই কাজটিই করছে:

body   { font-family: sans-serif; }
.cards { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-start; }
.card  { width: 13rem; padding: .75rem; border: 1px solid #8a8a8a; border-radius: 8px; }
.card h4 { margin: 0 0 .4rem; }
.card p  { margin: 0 0 .5rem; }
button { font: inherit; padding: .4rem .8rem; }

.fixed { height: 150px; overflow: hidden; }   /* ভঙ্গুর */
.fluid { min-height: 9rem; }                  /* মজবুত */

/* WCAG 1.4.12 পরীক্ষা — text spacing bookmarklet-এর মান */
* { line-height: 1.5 !important;
    letter-spacing: 0.12em !important;
    word-spacing: 0.16em !important; }
p { margin-bottom: 2em !important; }

বাঁয়ের কার্ডে বোতামটি অর্ধেক বা পুরো কেটে গেছে — ব্যবহারকারী জিনিসটি কিনতেই পারবেন না; ডানের কার্ড শুধু একটু লম্বা হয়েছে, কিছুই হারায়নি। চেষ্টা করুন: (১) শেষ চার লাইন মুছে দিন — এবার দুটি কার্ডই ঠিক দেখায়; ভঙ্গুরতা চোখে পড়ে শুধু চাপ দিলে, সেজন্যই এই পরীক্ষা। (২) .fixed-এর height-কে min-height করুন — সমস্যা মিটে গেল। (৩) শেষ নিয়মগুলো রেখে .card-এর width-কে 200px আর font-size: 20px যোগ করে দেখুন, লম্বা শব্দ কীভাবে বাক্সে আঁটে।

বাংলা লেখার একটি বিশেষ কথা মনে রাখুন: বাংলায় মাত্রা আর ওপর-নিচের কার (যেমন ি, ু, ৃ) লাইনের উচ্চতা বেশি চায়, তাই পর্ব ২১-এ দেখেছেন বাংলায় line-height সাধারণত ১.৬–১.৮ রাখা ভালো। স্থির উচ্চতার বাক্সে বাংলা লেখা তাই ইংরেজির চেয়ে আগে কাটে।

৭ · আঙুল, মাউস আর hover — target size আর pointer-নির্ভর নকশা

পর্ব ৩০-এ touch target-এর কথা বলেছিলাম; WCAG 2.2 এখন এর একটি সংখ্যা দিয়েছে। 2.5.8 Target Size (Minimum, AA): ক্লিক বা চাপার জায়গা অন্তত 24×24 CSS px — অথবা ছোট হলে আশপাশে এতটা ফাঁক যে প্রতিটির কেন্দ্রে 24px ব্যাসের একটি বৃত্ত আঁকলে বৃত্তগুলো অন্য লক্ষ্যকে বা একে অপরকে ছোঁয় না। লেখার ভেতরের লিংক, ব্রাউজারের নিজের কন্ট্রোল, আর যেখানে মাপটাই অপরিহার্য (যেমন মানচিত্রের পিন), সেখানে ছাড় আছে। AAA স্তরে (2.5.5) মাপটি 44×44.

আরেকটি ফাঁদ hover. যে নকশা শুধু মাউস রাখলে কাজের বোতাম দেখায়, তা টাচস্ক্রিনে আর কি-বোর্ডে অদৃশ্য। পর্ব ৩১-এর hover / pointer media feature দিয়ে যন্ত্র চেনা যায়, কিন্তু নিরাপদ পথ হলো এমনভাবে লেখা যাতে hover শুধু একটি “বাড়তি সুবিধা” থাকে:

body { font-family: sans-serif; }
.pager a { outline: 1px dashed #f97316; margin-right: 2px; }
.tiny a  { font-size: 12px; padding: 0 2px; }
.ok a    { display: inline-grid; place-items: center;
           min-width: 24px; min-height: 24px; padding: 4px 8px; }

.orders li { padding: 8px; border-bottom: 1px solid #e5e7eb; }
/* ভুল: বোতাম শুধু মাউস রাখলে দেখা যায় */
.actions { opacity: 0; }
.orders li:hover .actions { opacity: 1; }

/* ঠিক: কি-বোর্ডে ভেতরে focus এলে, আর hover-হীন যন্ত্রে সবসময় */
.orders li:focus-within .actions { opacity: 1; }
@media (hover: none) { .actions { opacity: 1; } }

কমলা ডটেড দাগ প্রতিটি লিংকের আসল চাপার জায়গা দেখাচ্ছে — ওপরের সারিতে খুদে, আঙুলে একটির জায়গায় আরেকটি চাপা পড়বেই; নিচের সারিতে অন্তত 24px. অর্ডারের তালিকায় মাউস রাখলে “বাতিল” বোতাম আসে; প্রিভিউতে ক্লিক করে Tab চাপলেও আসে (:focus-within). চেষ্টা করুন: (১) :focus-within-এর লাইনটি মুছে Tab চাপুন — অদৃশ্য বোতামে focus যাচ্ছে, ব্যবহারকারী দেখছেন না কী চাপছেন। (২) .ok a-র min-width আর min-height-কে 44px করুন — AAA মাপ কতটা জায়গা নেয় দেখুন।

hover-এ যা আসে — tooltip, ড্রপডাউন মেনু, ছোট পপআপ — তার জন্য আছে 1.4.13 Content on Hover or Focus (AA): (ক) বন্ধ করা যায় — মাউস না সরিয়েই Esc চাপলে চলে যায়; (খ) ধরা যায় — মাউস tooltip-এর ওপর নিলেও সেটি হারায় না; (গ) থাকে — ব্যবহারকারী সরিয়ে না দেওয়া পর্যন্ত নিজে থেকে মিলিয়ে যায় না। শুধু CSS-এর :hover দিয়ে বানানো tooltip প্রায়ই (ক) পূরণ করে না; পর্ব ১১-এর popover বা ছোট JavaScript লাগে।

৮ · লুকানো, ক্রম আর প্রিন্ট — চেনা নিয়ম, নতুন চোখে

পর্ব ২৩-এ লুকানোর ছয় উপায় দেখেছেন। অ্যাক্সেসিবিলিটির চোখে সংক্ষেপে আবার দেখি, কারণ ভুল উপায়ে লুকানো সবচেয়ে নীরব বাগগুলোর একটি:

উপায়চোখেscreen reader-এTab-একখন
display: none, hidden attributeনেইনেইনেইসত্যিই সবার কাছ থেকে লুকানো (বন্ধ মেনু)
visibility: hiddenনেই, জায়গা থাকেনেইনেইজায়গা ধরে রেখে লুকানো
visually-hidden ক্লাসনেইআছেআছে (focus পেলে)শুধু screen reader-এর জন্য লেখা — আইকন-বোতামের নাম
aria-hidden="true"আছেনেইআছে!শুধু নিছক সাজের আইকন — ভেতরে focus পাওয়ার মতো কিছু থাকলে চলবে না
inertআছেনেইনেইmodal খোলা থাকলে পেছনের পাতা
opacity: 0নেইআছেআছেশুধু অ্যানিমেশনের মাঝের অবস্থা — নইলে ফাঁদ

শেষ সারিটি অংশ ৭-এর “বাতিল” বোতামে দেখেছেন — opacity: 0-এ বোতাম অদৃশ্য কিন্তু Tab-এ আসে। স্ক্রিনের বাইরে সরানো বন্ধ মেনুর লিংকও তাই Tab-এ ধরা দেয় — বন্ধ মেনু display: none বা visibility: hidden দিয়ে লুকান।

চোখের ক্রম বনাম পড়ার ক্রম

পর্ব ২৫–২৮-এ order, flex-direction: row-reverse, grid-এর জায়গা বদল আর grid-auto-flow: dense দেখেছেন। এগুলো শুধু চোখের ক্রম বদলায়; screen reader আর Tab চলে HTML-এর ক্রমে। ফলে visual order আর DOM-এর ক্রম আলাদা হলে কি-বোর্ড ব্যবহারকারী পাতায় এলোমেলো লাফ দেন — WCAG 1.3.2 আর 2.4.3-এর লঙ্ঘন। নিয়মটি সহজ: অর্থের ক্রম HTML-এ ঠিক করুন, CSS-এ নয়।

এই সমস্যার CSS-সমাধান হিসেবে নতুন reading-flow property প্রস্তাবিত হয়েছে — যেমন reading-flow: grid-rows লিখলে focus আর screen reader grid-এর চোখের সারি মেনে চলবে। এটি এখন শুধু Chromium-ভিত্তিক ব্রাউজারের নতুন সংস্করণে আছে, not yet Baseline — অন্য ব্রাউজারে কোনো প্রভাব নেই। তাই একে বাড়তি সুরক্ষা ভাবুন, HTML-এর ক্রম ঠিক করার বিকল্প নয়।

প্রিন্টেও অ্যাক্সেসিবিলিটি

রশিদ, টিকিট, ভর্তির ফর্ম, রেসিপি — এখনো অনেকে ওয়েবপেজ প্রিন্ট করেন, আর কাগজে hover নেই, লিংকে ক্লিক নেই, রঙিন প্রিন্টারও সবার নেই। পর্ব ৩১-এর print stylesheet-এ অ্যাক্সেসিবিলিটির চোখে কয়েকটি কাজ:

@media print {
  nav, .ads, .chat-button, video { display: none; }       /* কাগজে অপ্রয়োজনীয় */
  body { color: #000; background: #fff; font-size: 12pt; line-height: 1.5; }
  a[href^="http"]::after { content: " (" attr(href) ")"; }  /* লিংকের ঠিকানা লেখা */
  .status-paid::before { content: "✔ পরিশোধিত — "; }        /* রঙের অর্থ লেখায় */
  h2, h3 { break-after: avoid; }   table, figure { break-inside: avoid; }
}

সাদা-কালো প্রিন্টে সবুজ “পরিশোধিত” আর লাল “বাকি” একই ধূসর হয়ে যায় — অংশ ২-এর 1.4.1 কাগজেও খাটে। আর পটভূমির রং ডিফল্টভাবে প্রিন্ট হয় না, তাই প্রিন্টে লেখা গাঢ় রাখুন।

৯ · পরীক্ষা — DevTools, Lighthouse আর নিজের কি-বোর্ড

অ্যাক্সেসিবল CSS লেখা অর্ধেক কাজ; বাকি অর্ধেক হলো যাচাই। এর জন্য সেটিং বদলে ঘুরে বেড়ানোর দরকার নেই — DevTools প্রায় সব অবস্থা অনুকরণ করতে পারে:

  1. Rendering প্যানেল (Chrome বা Edge-এ F12, তারপর Ctrl+Shift+P চেপে “Show Rendering”): Emulate CSS media feature থেকে prefers-reduced-motion: reduce, prefers-contrast: more, forced-colors: active, prefers-color-scheme — প্রতিটি আলাদা করে চালু করে পাতা দেখুন।
  2. দৃষ্টির সীমাবদ্ধতার অনুকরণ: একই প্যানেলে Emulate vision deficiencies — ঝাপসা দৃষ্টি, কম contrast-এর দৃষ্টি, লাল-সবুজ ও নীল-হলুদ বর্ণান্ধতার কয়েকটি ধরন, আর পুরো সাদা-কালো দৃষ্টি।
  3. zoom আর ৩২০px: Ctrl++ দিয়ে ২০০% ও ৪০০%; device toolbar-এ ৩২০px চওড়া।
  4. স্বয়ংক্রিয় পরীক্ষা: Lighthouse-এর Accessibility অংশ, বা axe DevTools extension — contrast, নামহীন বোতাম, ভুল ARIA-র মতো ধরা যায় এমন ভুল ধরে। Lighthouse-এর অ্যাক্সেসিবিলিটি পরীক্ষাগুলো ভেতরে axe-core-এর ওপরই দাঁড়িয়ে আছে।
  5. নিজের হাতে: মাউস সরিয়ে রেখে শুধু Tab, Shift+Tab, Enter, Space আর Esc দিয়ে পুরো কাজটি (খোঁজা, কার্টে নেওয়া, অর্ডার) শেষ করুন।

স্বয়ংক্রিয় টুলের সীমা: Lighthouse-এ ১০০ মানে “যা যন্ত্রে মাপা যায়, তাতে ভুল পাওয়া যায়নি” — “পাতা অ্যাক্সেসিবল” নয়। focus ঢাকা পড়া, এলোমেলো ক্রম বা text spacing-এ কাটা লেখা বেশিরভাগ টুল ধরতে পারে না — স্বয়ংক্রিয় পরীক্ষা শুরু, শেষ নয়।

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

  • WCAG AA-তে লেখার contrast কত? — সাধারণ লেখা ৪.৫:১, বড় লেখা (২৪px বা প্রায় ১৮.৬৬px বোল্ড) ৩:১; UI-এর সীমানা ও focus-এর দাগ ৩:১।
  • :focus আর :focus-visible-এর পার্থক্য? — প্রথমটি যেকোনো focus-এ, দ্বিতীয়টি শুধু যখন ব্রাউজার মনে করে দাগ দেখানো দরকার (যেমন কি-বোর্ডে); তাই মাউস-ক্লিকে বিরক্তি ছাড়াই কি-বোর্ডে দাগ রাখা যায়।
  • focus-এর দাগ box-shadow-এ কেন যথেষ্ট নয়? — forced colors মোডে box-shadow মুছে যায়; outline থাকে, বা অন্তত outline: 2px solid transparent রাখতে হয়।
  • লেখার মাপ px না rem? — rem; ব্যবহারকারীর ডিফল্ট font size-এর পছন্দ মানে, px তা উপেক্ষা করে।
  • sticky header focus ঢেকে দিলে সমাধান? — html { scroll-padding-top: <header-এর উচ্চতা>; } — WCAG 2.4.11.

১০ · AI-এর যুগে — অ্যাক্সেসিবল CSS-এর কাজ AI-কে দিলে

AI-কে “সুন্দর একটি কার্ড বানাও” বললে সে প্রায়ই দেয় হালকা ধূসর লেখা, ফিকে সীমানা, outline: none, px-এ স্থির উচ্চতা, আর হোভারে লাফানো অ্যানিমেশন — কারণ ইন্টারনেটের বহু জনপ্রিয় নমুনাই এমন। AI নিজের কোড Tab চেপে দেখে না, contrast মাপে না — তাই শর্ত আগেই বলুন, পরে যাচাই করুন।

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

AI-এর ভুলআপনি যা দেখবেনঠিক উপায়
*:focus { outline: none } বা reset-এ outline: 0Tab চাপলে কোথায় আছেন বোঝা যায় না:focus-visible-এ স্পষ্ট outline + offset
focus শুধু box-shadow-এforced colors-এ দাগ নেইoutline-এ মূল দাগ, বা transparent outline
#999, #aaa ধূসর লেখা, ফিকে placeholderরোদে বা বয়স্ক চোখে অপাঠ্য৪.৫:১ মেপে রং বাছা
অ্যানিমেশন সরাসরি, reduce অংশ নেইreduce motion চালু থাকলেও সব নড়েঅ্যানিমেশন no-preference-এর ভেতরে
html { font-size: 62.5% }-এর সাথে px-এ সব, বা font-size: 14pxব্রাউজারের font size বাড়ালেও লেখা ছোটrem, root-এ px নয়
কার্ডে height: 200px; overflow: hiddenzoom বা text spacing-এ লেখা কাটেmin-height, স্বাভাবিক উচ্চতা
শুধু :hover-এ দেখানো বোতাম, ২০px-এর আইকনফোনে আর কি-বোর্ডে অদৃশ্য, ভুল চাপ:focus-within, @media (hover: none), 24px+

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

তুমি একজন অভিজ্ঞ ফ্রন্ট-এন্ড ও অ্যাক্সেসিবিলিটি বিশেষজ্ঞ। আমার বাংলা অনলাইন দোকানের পণ্য-কার্ড আর "কার্টে নিন" বোতামের CSS লিখে দাও। শর্ত (WCAG 2.2 AA): ১. লেখার contrast অন্তত 4.5:1, বোতামের সীমানা ও আইকন 3:1 — প্রতিটি রঙের জোড়ার ratio হিসাব করে মন্তব্যে লেখো। ২. outline: none লিখবে না; :focus-visible-এ অন্তত 2px পুরু outline আর outline-offset, যা সাদা ও গাঢ় দুই পটভূমিতেই দেখা যায়। ৩. লেখা ও ফাঁক rem-এ; html-এ px-এ font-size নয়; লেখার পাত্রে স্থির height বা overflow: hidden নয়। ৪. সব অ্যানিমেশন @media (prefers-reduced-motion: no-preference)-এর ভেতরে। ৫. @media (forced-colors: active)-এ সীমানা ঠিক থাকে, শুধু box-shadow-এ কিছু আঁকবে না। ৬. বোতাম অন্তত 24×24 CSS px; hover-এ লুকানো কোনো কাজ নেই। ৭. HTML-এর ক্রম আর চোখের ক্রম একই থাকবে। শেষে একটি পরীক্ষার তালিকা দাও: কোন কোন DevTools অনুকরণে আর কী কী মাপে আমি নিজে যাচাই করব।

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

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

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

প্র ০১outline: none এত ক্ষতিকর হলে এটি ওয়েবে এত ছড়াল কীভাবে? আর :focus-visible কি সমস্যাটি পুরো মিটিয়েছে?
  • reset stylesheet-এর যুগ: ২০০০-এর দশকে ব্রাউজারভেদে ডিফল্ট style অনেক আলাদা ছিল, তাই ডেভেলপাররা সব ডিফল্ট “শূন্য” করে দেওয়া reset ব্যবহার করতেন। Eric Meyer-এর জনপ্রিয় reset-এর প্রথম দিকের সংস্করণে :focus { outline: 0; } ছিল — পাশে মন্তব্য, “focus-এর style নিজে দিতে ভুলবেন না!” লাখো সাইট লাইনটি কপি করল, মন্তব্যটি নয়। পরে reset-এর সংস্করণ থেকে লাইনটি সরানো হয়, কিন্তু ততদিনে অভ্যাস ছড়িয়ে গেছে।
  • ডিজাইনারের যুক্তিসঙ্গত বিরক্তি: পুরোনো ব্রাউজার মাউসে ক্লিক করলেও বোতামের চারপাশে দাগ দেখাত। মুছে ফেলাই ছিল সহজ পথ — আর যিনি মুছলেন, তিনি নিজে হয়তো কখনো কি-বোর্ডে সাইট চালাননি।
  • :focus-visible-এর সমাধান: ব্রাউজার নিজেই যদি বুঝে নেয় কখন দাগ দরকার, তাহলে মোছার যুক্তি থাকে না। প্রথমে একটি JavaScript polyfill হিসেবে ধারণাটি জনপ্রিয় হয়, তারপর Selectors Level 4-এ জায়গা পায়, আর এখন সব প্রধান ব্রাউজারে আছে; ব্রাউজারের নিজের ডিফল্ট দাগও এখন এই নিয়মে চলে।

তবু সমস্যা পুরো মেটেনি। প্রথমত, পুরোনো outline: none লক্ষ লক্ষ stylesheet-এ এখনো আছে, আর AI সেগুলো থেকেই শেখে। দ্বিতীয়ত, ব্রাউজারের ডিফল্ট দাগ সব পটভূমিতে স্পষ্ট নয় — ব্র্যান্ডের নীল পটভূমিতে নীল দাগ হারিয়ে যায়; তাই নিজের দাগ নকশা করা এখনো আপনার দায়িত্ব। তৃতীয়ত, দাগ থাকলেও sticky header তাকে ঢেকে দিতে পারে — এজন্যই WCAG 2.2 আলাদা করে 2.4.11 যোগ করেছে। আর চতুর্থত, “মাউস ব্যবহারকারীর দাগ লাগে না” ধারণাটিও পুরো ঠিক নয়: যিনি মাউস আর কি-বোর্ড মিশিয়ে চালান, তাঁর জন্য লেখার ঘরের দাগ সবসময় দরকার, যেজন্য input-এ :focus-visible সবসময় সত্য।

মূল উপলব্ধি: নকশার যেকোনো “শুধু মুছে দাও” সমাধানের আগে প্রশ্ন করুন — এটি কার কাজে লাগছিল? প্রায় সবসময় ভালো উত্তর হলো মুছে ফেলা নয়, আরও ভালো করে আঁকা।

প্র ০২৪.৫:১ সংখ্যাটি কোথা থেকে এলো? আর এই সূত্র যদি নিখুঁত না হয়, তবু আমরা এটি মানব কেন?

সংখ্যাটি হঠাৎ বানানো নয়। WCAG-এর ব্যাখ্যা-নথি (Understanding WCAG) অনুযায়ী এর ভিত্তি একটি পুরোনো আন্তর্জাতিক মান: পর্দার লেখা পড়ার জন্য ISO-9241-3-এ সাধারণ দৃষ্টির মানুষের জন্য অন্তত ৩:১ contrast সুপারিশ করা হয়েছিল। WCAG-এর লেখকরা হিসাব করলেন, বয়সজনিত বা মাঝারি কম দৃষ্টিশক্তিতে (মোটামুটি ২০/৪০ দৃষ্টির সমান) contrast টের পাওয়ার ক্ষমতা যতটা কমে, তা পুষিয়ে দিতে ৩:১-কে বাড়িয়ে ৪.৫:১ করতে হয়। আর আরও কম দৃষ্টির (মোটামুটি ২০/৮০) জন্য একইভাবে আসে ৭:১ — যা AAA. বড় লেখা নিজেই পড়া সহজ, তাই তার সীমা এক ধাপ নিচে।

সূত্রের সীমাও আছে, আর পেশাদারদের তা জানা উচিত:

  • আকার আর ওজন দেখে না: খুব সরু (light weight) ১৪px লেখা ৪.৫:১-এ পাস করেও কষ্টকর, আর মোটা বোল্ড লেখা কম ratio-তেও পড়া যায়।
  • গাঢ় রঙের দিকে দুর্বল: গাঢ় পটভূমিতে সূত্রটি অনেক সময় এমন জোড়া পাস করায় যা চোখে কষ্টকর, আবার কিছু ভালো জোড়া ফেল করায়। কমলা বা মাঝারি নীল বোতামে সাদা না কালো লেখা — এই নিয়ে সূত্র আর মানুষের পছন্দ প্রায়ই মেলে না।
  • নতুন পদ্ধতি এখনো খসড়ায়: এসব কারণে WCAG 3-এর আলোচনায় APCA নামে একটি পদ্ধতি পরীক্ষা হয়েছে, যা লেখার আকার-ওজন আর সামনের-পেছনের রং আলাদা ধরে হিসাব করে। কিন্তু WCAG 3 এখনো চূড়ান্ত নয়।

তাহলে মানব কেন? (১) আইন ও চুক্তির ভাষা — বহু দেশের সরকারি সাইটের নিয়ম আর বড় কোম্পানির ক্রয়-শর্তে WCAG 2.x AA-ই লেখা; (২) মাপা যায় — দুজন অডিটর একই রায় দেবেন; (৩) এটি মেঝে, ছাদ নয় — পাস করার পরও সরু ফন্ট বা গাঢ় থিমে চোখে দেখে আরও গাঢ় করা যায়।

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

প্র ০৩reduce motion চালু থাকলে সব অ্যানিমেশন বন্ধ করে দেওয়াই কি সবচেয়ে নিরাপদ নয়? “reduce” আর “remove”-এর পার্থক্য কেন গুরুত্বপূর্ণ?

“সব বন্ধ” শুনতে নিরাপদ, কিন্তু এতে দুটি ভিন্ন সমস্যা গুলিয়ে যায়: কোন নড়াচড়া ক্ষতি করে, আর কোন নড়াচড়া তথ্য দেয়।

  • কোনগুলো ঝুঁকিপূর্ণ: vestibular সমস্যায় সবচেয়ে কষ্ট দেয় এমন নড়াচড়া, যা মস্তিষ্ককে “আমি নড়ছি” ভাবায় অথচ শরীর স্থির — বড় এলাকা জুড়ে জুম, ঘূর্ণন, parallax, স্ক্রলের সাথে বেগে সরা পটভূমি, পাতাজোড়া স্লাইড। বাসে বসে বই পড়লে মাথা ঘোরার মতোই — চোখ আর কান দুই রকম খবর দেয়।
  • কোনগুলো সাধারণত নিরাপদ ও কাজের: রং বা স্বচ্ছতার ধীর পরিবর্তন, ছোট এলাকার দ্রুত পরিবর্তন — যেমন বোতাম চাপলে রং বদল, নতুন বার্তা ধীরে ফুটে ওঠা, লোডিংয়ের অবস্থা বোঝানো। এগুলো বন্ধ করলে ব্যবহারকারী বুঝতেই পারেন না কিছু ঘটেছে কি না।
  • “সব বন্ধ”-এর পার্শ্বপ্রতিক্রিয়া: 0.01ms-এর reset JavaScript-নির্ভর UI-তে অদ্ভুত ঝাঁকুনি আনতে পারে, carousel-এর মতো কম্পোনেন্টে এক লাফে শেষ অবস্থায় পৌঁছে দেয়, আর scroll-driven animation-এ এমন অবস্থা তৈরি করতে পারে যেখানে লেখা কখনো দৃশ্যমানই হয় না।

তাই পেশাদার পথ হলো প্রতিটি অ্যানিমেশনকে আলাদা করে প্রশ্ন করা: এটি কি অর্থ বহন করে? করলে reduce-এ অর্থটি অন্যভাবে (fade, রং, লেখা) দিন; না করলে বাদ দিন। আর “আগে স্থির” নীতিতে লিখলে ডিফল্টই নিরাপদ, তাই ভুলের খরচ কম। WCAG এখানে আংশিক নির্দেশনা দেয় — 2.2.2 (A) একটানা নড়া জিনিস থামানোর উপায় চায়, 2.3.1 (A) সেকেন্ডে তিনবারের বেশি ঝলকানি নিষেধ করে (খিঁচুনির ঝুঁকি), আর 2.3.3 (AAA) ব্যবহারকারীর কাজে শুরু হওয়া নড়াচড়া বন্ধ করার সুযোগ চায়। prefers-reduced-motion মানা 2.3.3 পূরণের সবচেয়ে প্রচলিত উপায়।

মূল উপলব্ধি: অ্যাক্সেসিবিলিটি মানে অভিজ্ঞতা কমিয়ে দেওয়া নয়, বরং একই তথ্য একেকজনের উপযোগী রূপে দেওয়া। reduce motion-এর লক্ষ্য শান্ত পাতা, মৃত পাতা নয়।

প্র ০৪forced colors মোড আমার যত্নে বানানো ব্র্যান্ডের রং সব মুছে দেয়। forced-color-adjust: none দিয়ে পুরো সাইটকে “রক্ষা” করলে দোষ কী?
  • এটি ব্যবহারকারীর সচেতন সিদ্ধান্ত: Windows বহু বছর ধরে, পুরোনো সংস্করণ থেকেই, High Contrast নামে এই ব্যবস্থা দিয়ে এসেছে; Windows 11-এ এর নাম Contrast themes. যিনি এটি চালু করেন, তিনি পুরো কম্পিউটারের — মেনু, ফাইল, ইমেইল, সব অ্যাপের — রং নিজের চোখের উপযোগী করে বেঁধে দিয়েছেন।
  • পুরো সাইট “রক্ষা” করা মানে তাঁকে বাদ দেওয়া: সব অ্যাপ তাঁর রঙে চলছে, শুধু আপনার সাইট হঠাৎ উজ্জ্বল সাদা আর ফিকে ধূসর — ঠিক যা থেকে বাঁচতে তিনি সেটিং চালু করেছিলেন। এটি অনেকটা চশমা পরা মানুষকে বলা, “আমার দোকানে চশমা খুলে ঢুকুন।”
  • ইতিহাসের শিক্ষা: পুরোনো Internet Explorer আর Edge-এ Microsoft-এর নিজস্ব -ms-high-contrast media query দিয়ে ডেভেলপাররা এই মোড সামলাতেন। পরে ধারণাটি মানসম্মত হয়ে forced-colors আর forced-color-adjust নামে CSS-এর স্পেসিফিকেশনে আসে, যাতে সব ব্রাউজারে একই নিয়ম চলে। স্পেসিফিকেশন নিজেই forced-color-adjust: none-কে সীমিত ব্যবহারের জন্য রেখেছে।

তাহলে ব্র্যান্ডের কী হবে? forced colors-এ ব্র্যান্ড বাঁচে আকারে — লোগোর গঠন, লেআউট, টাইপোগ্রাফি, আইকনের নকশায় — রঙে নয়। আপনার কাজ নিশ্চিত করা যে রং চলে গেলেও কাঠামো থাকে: সীমানা border-এ, অবস্থা লেখায় বা আইকনে, focus outline-এ, নির্বাচিত অবস্থা Highlight-এ। ব্যতিক্রম কেবল সেখানে, যেখানে রংটাই বিষয়বস্তু — রঙের নমুনা, পতাকা, চার্টের কিংবদন্তির ছোট বর্গ। সেখানে ছোট এলাকায় forced-color-adjust: none ঠিক আছে, সাথে লেখায় রঙের নাম দিলে আরও ভালো।

মূল উপলব্ধি: ব্যবহারকারীর সেটিং আর লেখকের CSS-এর লড়াইয়ে ওয়েবের নকশা-নীতি পরিষ্কার — ব্যবহারকারী জেতেন। ভালো লেখক সেই জয়কে সহজ করেন, ঠেকান না।

প্র ০৫Lighthouse-এ ১০০ পেলাম, তবু একজন ব্যবহারকারী লিখলেন সাইটটি তিনি চালাতে পারছেন না। কীভাবে সম্ভব? আর “এক লাইনে পুরো সাইট অ্যাক্সেসিবল” করার overlay পণ্যগুলো কি সমাধান?

খুবই সম্ভব — কারণ স্বয়ংক্রিয় টুল যা মাপতে পারে আর মানুষ যা অনুভব করে, তার মধ্যে বড় ফাঁক আছে।

  • যন্ত্র যা পারে: contrast-এর সংখ্যা, নামহীন বোতাম, alt-হীন ছবি, ভুল ARIA, lang নেই — এমন নিয়মভিত্তিক ভুল।
  • যন্ত্র যা পারে না: focus-এর দাগ চোখে পড়ার মতো কি না, sticky header তাকে ঢাকছে কি না, Tab-এর ক্রম যুক্তিসঙ্গত কি না, alt লেখাটি সঠিক কি না (শুধু আছে কি না নয়), অ্যানিমেশন অস্বস্তিকর কি না, ২০০% zoom বা text spacing-এ কিছু কাটছে কি না, ভুলের বার্তা বোঝা যায় কি না। অ্যাক্সেসিবিলিটি-বিশেষজ্ঞদের মধ্যে প্রচলিত হিসাব হলো, স্বয়ংক্রিয় টুল সব সমস্যার কেবল একটি অংশ — সাধারণত অর্ধেকের অনেক কম — ধরতে পারে। আর Lighthouse-এর স্কোর শুধু সেই পরীক্ষাগুলোর ওপর, যা সে চালায়।
  • Overlay: কিছু কোম্পানি একটি script বসিয়ে “স্বয়ংক্রিয়ভাবে সাইট অ্যাক্সেসিবল” করার দাবি করে — পাশে একটি ভাসমান বোতাম, যাতে লেখা বড় করা বা contrast বদলানোর বিকল্প। কিন্তু এসব বিকল্প ব্রাউজার আর অপারেটিং সিস্টেমে আগেই আছে, আর আসল সমস্যা — ভুল HTML, ভাঙা ক্রম, অদৃশ্য focus — script দিয়ে নির্ভরযোগ্যভাবে সারে না; অনেক screen reader ব্যবহারকারী জানিয়েছেন overlay তাঁদের টুলের সাথে সংঘাতও বাধায়। বহু অ্যাক্সেসিবিলিটি পেশাজীবী “Overlay Fact Sheet” নামের প্রকাশ্য বিবৃতিতে এর বিরুদ্ধে অবস্থান নিয়েছেন।

তাহলে পথ কী? স্তরে স্তরে পরীক্ষা: (১) লেখার সময় সঠিক HTML আর এই পর্বের CSS-নিয়ম; (২) স্বয়ংক্রিয় টুল — দ্রুত, সস্তা, প্রতিবার; (৩) নিজের হাতে কি-বোর্ড, zoom, অনুকরণ — প্রতিটি নতুন কম্পোনেন্টে; (৪) বড় প্রকাশের আগে screen reader দিয়ে পরীক্ষা; (৫) সম্ভব হলে প্রতিবন্ধী ব্যবহারকারীদের নিয়ে আসল ব্যবহার-পরীক্ষা। যিনি অভিযোগ করেছেন, তিনি বিনা পয়সায় সবচেয়ে দামি পরীক্ষাটি দিয়েছেন।

মূল উপলব্ধি: স্কোর একটি থার্মোমিটার, চিকিৎসা নয়। অ্যাক্সেসিবিলিটি কোনো ফিচার নয় যা শেষে “যোগ” করা যায় — এটি প্রতিটি সিদ্ধান্তের একটি মান, যা মানুষ দিয়েই শেষ পর্যন্ত যাচাই হয়।

অনুশীলন

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

  1. মাউস ছাড়া এক দিন: আপনার প্রতিদিনের তিনটি সাইট (একটি অনলাইন দোকান, একটি সংবাদ সাইট, একটি ব্যাংক বা মোবাইল ব্যাংকিংয়ের ওয়েবসাইট) শুধু Tab, Shift+Tab, Enter আর Esc দিয়ে চালান। কোথায় focus হারায়, কোথায় ঢাকা পড়ে, কোথায় ক্রম এলোমেলো? লিখে রাখুন।

    সাধারণত যা পাবেন: (ক) মেনু, কার্ড বা নিজের বানানো বোতামে কোনো দাগ নেই — Elements প্যানেলে সেই element বেছে Styles-এ :focus বা outline: none/0 খুঁজুন (Styles প্যানেলের :hov বোতাম দিয়ে :focus-visible অবস্থা জোর করে দেখা যায়); (খ) sticky header-এর নিচে focus লুকায় — scroll-padding দেখুন; (গ) লুকানো মেনুর লিংকে focus যায় কিন্তু দেখা যায় না — opacity বা স্ক্রিনের বাইরে সরানো দিয়ে লুকানো; (ঘ) modal খুললে Tab পেছনের পাতায় চলে যায় — inert বা <dialog> নেই।

  2. text spacing-এর চাপ পরীক্ষা: একটি পরিচিত সাইটে DevTools-এর Console-এ নিচের এক লাইনের কোড চালিয়ে বাংলা ও ইংরেজি অংশ দেখুন: document.head.insertAdjacentHTML('beforeend', '<style>*{line-height:1.5!important;letter-spacing:.12em!important;word-spacing:.16em!important}p{margin-bottom:2em!important}</style>'). কী কাটল বা ঢাকা পড়ল?

    বেশি দেখা যায়: স্থির উচ্চতার বোতামে লেখা কেটে যাওয়া, এক লাইনে আঁটানো মেনু উপচে পড়া বা পরের element-এর ওপর চড়ে বসা, কার্ডের শিরোনাম text-overflow: ellipsis-এ লুকিয়ে যাওয়া, ব্যাজ বা ট্যাবের লেখা বাক্সের বাইরে যাওয়া। প্রতিটির কারণ খুঁজতে Elements প্যানেলে সেই element-এর Computed-এ height, max-height, overflow দেখুন। সমাধান প্রায় সবসময় একই পরিবারের: height-এর বদলে min-height, flex-wrap: wrap, padding-এ উচ্চতা, আর লেখার পাত্রে overflow: hidden সরানো।

  3. বাগ খুঁজুন: একটি সাইটের বোতামের CSS: .btn { border: 0; background: #2563eb; color: #fff; } .btn:focus { outline: none; box-shadow: 0 0 0 3px #93c5fd; }. Windows-এর contrast theme চালু থাকা একজন ব্যবহারকারী বললেন, তিনি বোতাম চিনতে পারছেন না আর Tab-এ কোথায় আছেন বুঝছেন না। দুটি সমস্যার কারণ কী, আর ঠিক CSS কী?

    (১) বোতামের সীমানা শুধু পটভূমির রঙে — forced colors-এ পটভূমি ব্যবহারকারীর রঙে বদলায় আর border: 0 থাকায় বোতামটি আশপাশের লেখার সাথে মিশে যায়। (২) focus শুধু box-shadow-এ, যা forced colors-এ মুছে যায়, আর outline: none ব্রাউজারের নিজের দাগও সরিয়েছে। সমাধান:

    .btn { border: 2px solid transparent;    /* forced colors-এ দৃশ্যমান হয় */
           background: #2563eb; color: #fff; }
    .btn:focus-visible { outline: 3px solid #1e3a8a; outline-offset: 2px;
                         box-shadow: 0 0 0 6px #ffffff; }   /* ছায়া শুধু বাড়তি সাজ */

    যাচাই: DevTools-এর Rendering প্যানেলে forced-colors: active অনুকরণ করে Tab চাপুন — বোতামের সীমানা আর focus-এর দাগ দুটোই দেখা যাবে।

  4. নিজে লিখুন — শান্ত লোডার: একটি লোডার বানান যা স্বাভাবিক অবস্থায় ঘোরে, কিন্তু reduce motion চাইলে না ঘুরে ধীরে জ্বলে-নেভে (pulse), যাতে “কাজ চলছে” অর্থটি থাকে। “আগে স্থির” নীতিতে লিখুন, আর লোডারের পাশে লেখায় অবস্থাটি জানান।

    একটি সম্ভাব্য উত্তর — HTML: <p class="status" role="status"><span class="loader" aria-hidden="true"></span> অর্ডার পাঠানো হচ্ছে…</p>. CSS:

    body { font-family: sans-serif; }
    .status { display: flex; align-items: center; gap: .6rem; }
    .loader { width: 1.5rem; height: 1.5rem; border-radius: 50%;
              border: 3px solid #cbd5e1; border-top-color: #1d4ed8; }
    
    @media (prefers-reduced-motion: no-preference) {
      .loader { animation: spin 1s linear infinite; }
    }
    @media (prefers-reduced-motion: reduce) {
      .loader { animation: pulse 2s ease-in-out infinite; }
    }
    @keyframes spin  { to { transform: rotate(1turn); } }
    @keyframes pulse { 50% { opacity: .35; } }

    স্বাভাবিক অবস্থায় বৃত্তটি ঘোরে; reduce-এ জায়গা না বদলে শুধু উজ্জ্বলতা ধীরে ওঠানামা করে। লক্ষ করুন, লোডার নিজে aria-hidden, আর অর্থটি আছে লেখায় ও role="status"-এ — screen reader ব্যবহারকারী নড়াচড়া দেখেন না, বার্তা শোনেন। অ্যানিমেশন কাজ শেষ হলেই সরিয়ে দিন; অনির্দিষ্টকাল ঘোরা লোডারও WCAG 2.2.2-এর আওতায় পড়তে পারে। নিজের পাতায় দুই রূপ দেখতে DevTools-এর Rendering প্যানেলে prefers-reduced-motion বদলান।

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

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

ক্রমদুটি রঙের WCAG contrast ratio হিসাবের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🎨 hex রংকে লাল, সবুজ, নীলের তিনটি সংখ্যায় ভাঙা
  2. ➗ প্রতিটিকে ২৫৫ দিয়ে ভাগ করে ০–১-এর মাপে আনা
  3. 📈 gamma সরিয়ে আসল আলোর (linear) মান বের করা
  4. 👁️ চোখের ওজনে যোগ করে relative luminance পাওয়া
  5. ⚖️ (উজ্জ্বল + 0.05) ÷ (গাঢ় + 0.05) ভাগ করা
  6. ✅ ফলকে ৪.৫:১ বা ৩:১-এর সাথে মেলানো

WCAG 2.2-এর AA স্তরে 16px আকারের সাধারণ (বোল্ড নয়) লেখা আর তার পটভূমির ন্যূনতম contrast ratio কত?

  • ৩:১
  • ৭:১
  • ৪.৫:১
  • ২১:১

একটি বোতামের focus-এর দাগ শুধু box-shadow: 0 0 0 3px blue দিয়ে আঁকা, আর outline: none. Windows-এর contrast theme (forced colors) চালু থাকলে কী ঘটবে?

  • ছায়াটি ব্যবহারকারীর থিমের রঙে আঁকা হবে
  • focus-এর কোনো দাগই দেখা যাবে না
  • ব্রাউজার নিজের ডিফল্ট দাগ ফিরিয়ে আনবে
  • দাগটি আরও মোটা হয়ে দেখা যাবে

“আগে স্থির” (no-motion-first) নীতিতে একটি স্লাইড-অ্যানিমেশন কোথায় লেখা হয়?

  • সাধারণ নিয়মে, তারপর @media (prefers-reduced-motion: reduce)-এ বন্ধ করা হয়
  • @media (prefers-reduced-motion: reduce)-এর ভেতরে
  • @media print-এর ভেতরে
  • @media (prefers-reduced-motion: no-preference)-এর ভেতরে

পাতায় 60px উঁচু একটি sticky header আছে। Shift+Tab চাপলে focus পাওয়া লিংক header-এর নিচে লুকিয়ে যায়। কোন CSS এই সমস্যার সবচেয়ে সরাসরি সমাধান?

  • html { scroll-padding-top: 60px; }
  • header { z-index: 9999; }
  • header { padding-bottom: 60px; }
  • a:focus { outline-offset: 60px; }

একটি বোতামে aria-hidden="true" দেওয়া, কিন্তু CSS-এ সেটি দৃশ্যমান। এর ফল কী?

  • বোতামটি চোখে আর Tab-এ — দুই জায়গা থেকেই হারিয়ে যায়
  • বোতামটি দেখা যায় আর Tab-এ focus পায়, কিন্তু screen reader তাকে ঘোষণা করে না
  • বোতামটি দেখা যায়, কিন্তু ক্লিক করলে কাজ করে না
  • বোতামটি চোখে লুকায়, কিন্তু screen reader পড়ে

ব্যবহারকারী ব্রাউজারের ডিফল্ট font size 16px থেকে 24px করেছেন (zoom নয়)। কোন CSS-এ লেখা তাঁর এই পছন্দ মেনে বড় হবে?

  • p { font-size: 16px; }
  • html { font-size: 16px; } p { font-size: 1rem; }
  • p { font-size: 1rem; }, আর html-এ কোনো font-size নেই
  • p { font-size: 1.2vw; }

শব্দকোষ

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

মনে রাখুন

  1. Contrast মাপুন: সাধারণ লেখা ৪.৫:১, বড় লেখা ৩:১, UI-এর সীমানা-আইকন-focus ৩:১; আর শুধু রঙে অর্থ নয় — লেখার ভেতরের লিংকে underline, ভুলে আইকন ও লেখা।
  2. Focus মুছবেন না, নকশা করুন: :focus-visible-এ outline আর outline-offset, দরকারে দুই রঙের দাগ; মূল দাগ box-shadow-এ নয়; sticky header-এর জন্য scroll-padding-top.
  3. নড়াচড়া “আগে স্থির”: অ্যানিমেশন prefers-reduced-motion: no-preference-এর ভেতরে; reduce মানে remove নয় — অর্থ থাকলে fade দিন; parallax আর বড় জুম এড়ান।
  4. ব্যবহারকারীর রং জেতে: prefers-contrast: more-এ আরও স্পষ্ট; forced colors-এ সীমানা border/outline-এ, রং system color-এ, forced-color-adjust: none শুধু রঙের নমুনায়।
  5. চাপ সয় এমন লেআউট: লেখা rem-এ, স্থির উচ্চতা নয়; ২০০% zoom, ৩২০px reflow আর text spacing-এ কিছু কাটে না; চাপার জায়গা অন্তত 24×24 px; hover-এ লুকানো কাজ নয়; ক্রম HTML-এ; শেষে DevTools, Lighthouse আর নিজের কি-বোর্ডে পরীক্ষা।

পরের পর্বে: এই পর্ব দিয়ে মোশন, ইফেক্ট আর পলিশের মডিউল শেষ। মডিউল ৭ শুরু হবে পেশাদার কাজের ধারা দিয়ে — বড় প্রজেক্টে শত শত পাতার CSS কীভাবে গুছিয়ে রাখবেন: BEM-এর নামকরণ, utility class, আর এমন architecture যেখানে focus-এর দাগ বা reduce motion-এর নিয়ম একবার লিখে পুরো সাইটে বসানো যায়।

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

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