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

position আর z-index — relative, absolute, fixed, sticky

CSS Positioning — relative, absolute, fixed, sticky, z-index & Stacking Contexts

Taught in Bangla, this lesson covers CSS positioning: relative offsets, absolute positioning against the containing block, fixed elements, sticky positioning and why it sometimes fails, z-index and stacking contexts, the top layer used by dialogs and popovers, real interface patterns like badges and sticky headers with accessibility in mind, and checking AI-generated positioning code.

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

  • পাঁচটি position মান — কোনটি flow-এ জায়গা রাখে, কোনটি ছেড়ে দেয়, কার সাপেক্ষে মাপ নেয়
  • containing block খোঁজার নিয়ম — absolute বাক্স কোথায় বসবে, আগে থেকে বলতে পারা
  • inset shorthand, absolute বাক্স মাঝখানে বসানোর দুই উপায়, আর fixed ও sticky-র লুকানো ফাঁদ
  • z-index, stacking context আর isolation — কেন z-index: 9999 হেরে যায়, আর কীভাবে সারাবেন
  • বাস্তব নকশা — ব্যাজ, dropdown, sticky header, modal overlay, tooltip — আর top layer-এর আধুনিক সমাধান

১ · position কী সমস্যার সমাধান করে — সারির বাইরে বসা

আগের পর্বে (পর্ব ২৩) দেখেছেন, ব্রাউজার পাতার element-গুলো একটি স্বাভাবিক নিয়মে সাজায়: block বাক্স একটির নিচে আরেকটি, inline লেখা লাইন ধরে বাঁ থেকে ডানে। এই নিয়মের নাম normal flow. পাতার বেশিরভাগ কাজ এই flow-ই করে দেয় — লেখা লম্বা হলে বাক্স নিজেই বড় হয়, পরের জিনিস নিজেই নিচে সরে।

কিন্তু কিছু নকশা এই সারিতে আঁটে না। অনলাইন দোকানের পণ্য-কার্ডের কোণে “-২০%” লেখা লাল ব্যাজ। স্ক্রল করলেও ওপরে আটকে থাকা মেনু। পাতার ওপর কালো আবরণ আর মাঝখানে “অর্ডার সফল হয়েছে” বাক্স। বোতামে মাউস রাখলে ছোট ব্যাখ্যা। এগুলো সারির নিয়ম ভাঙে — অন্য কিছুর ওপরে ভাসে, বা স্ক্রলের সাথে নড়ে না। এর জন্যই CSS-এর position property.

ঢাকার একটি বাসের কথা ভাবুন। সিটে সারি ধরে বসা যাত্রীরা হলো normal flow. relative হলো সেই যাত্রী, যিনি সিটে বসেই একটু হেলে জানালার দিকে ঝুঁকেছেন — সিট তাঁর নামেই আছে, কেউ সেখানে বসতে পারবে না। absolute হলো যিনি সিট ছেড়ে উঠে দাঁড়িয়েছেন — তাঁর সিট অন্য কেউ নিয়ে নিল, আর তিনি দাঁড়ালেন বাসের নির্দিষ্ট একটা খুঁটি ধরে। fixed হলো বাসের সামনের কাচে সাঁটা রুটের স্টিকার — বাস যতই চলুক, জানালার একই কোণে। আর sticky হলো কন্ডাক্টর, যিনি দরজার কাছে পৌঁছে সেখানে আটকে থাকেন — কিন্তু বাসের বাইরে যান না।

নিচের ডায়াগ্রামে একই তিনটি বাক্স — ক, খ, গ — রাখা হয়েছে। শুধু মাঝের লাল “খ”-এর position বদলাচ্ছে। প্রতিটি ধাপে দুটি জিনিস খেয়াল করুন: খ-এর আসল জায়গাটা খালি থাকছে কি না, আর খ কার সাপেক্ষে মাপ নিচ্ছে।

ক
খ · position: static
গ
ক
খ-এর আসল জায়গা — খালি থাকে
খ · relative · top 22 · left 48
গ
parent · position: relative
ক
গ — খ-এর জায়গায় উঠে এসেছে
খ · absolute
খ · fixed · top 0 — জানালায় আটকানো
ক — স্ক্রল হয়ে নিচে ঢুকে যাচ্ছে
গ
ঘ
ভিউপোর্ট · পাতা স্ক্রল হচ্ছে ↑
ক — স্ক্রল হয়ে ওপরে চলে গেছে
খ · sticky · top 0 — সীমায় পৌঁছে আটকেছে
গ
ঘ
parent শেষ হলে খ-ও চলে যাবে
  1. static — ডিফল্ট। কিছু না লিখলে সব element position: static. এরা normal flow মেনে চলে, আর top, left, z-index লিখলেও কোনো কাজ হয় না। “static” মানে এখানে “নড়ে না” নয় — মানে “বিশেষ কোনো position নেই”।
  2. relative — জায়গা রেখে সরে যাওয়া। খ নিজের আসল জায়গা থেকে top/left অনুযায়ী সরে গেল, কিন্তু আসল জায়গাটা দাগ দেওয়া ফাঁকা হিসেবে রয়ে গেল। গ টের-ই পেল না যে খ সরেছে। ফলে খ গ-এর ওপর আংশিক উঠে গেছে।
  3. absolute — flow থেকে বেরিয়ে যাওয়া। খ সারি ছেড়ে দিল, তাই গ উঠে এসে তার জায়গা নিল। খ এখন বসেছে বেগুনি দাগের parent-এর কোণে, কারণ সেই parent-এ position: relative আছে। parent-ই এখানে খ-এর মাপের ভিত্তি — একে বলে containing block.
  4. fixed — জানালার সাথে আটকানো। খ এখন ব্রাউজারের দৃশ্যমান জানালা, অর্থাৎ viewport-এর সাপেক্ষে বসে। পাতা স্ক্রল হলে ক, গ, ঘ ওপরে উঠে যায়, কিন্তু খ একই জায়গায় থাকে। flow-এ এর কোনো জায়গা নেই।
  5. sticky — সীমায় পৌঁছে আটকে যাওয়া। শুরুতে খ সাধারণ বাক্সের মতোই flow-এ থাকে। স্ক্রল করতে করতে যখন জানালার ওপর থেকে top: 0 দূরত্বে পৌঁছায়, তখন আটকে যায়। আর তার parent-এর শেষ প্রান্ত এসে গেলে খ-কেও সঙ্গে নিয়ে চলে যায়। এটি relative আর fixed-এর মিশ্রণ।
মানflow-এ জায়গা রাখে?top/left কার সাপেক্ষেনতুন stacking context?
staticহ্যাঁকাজ করে নানা
relativeহ্যাঁ (আসল জায়গা খালি থাকে)নিজের আসল জায়গাশুধু z-index দিলে
absoluteনাcontaining block (নিকটতম positioned পূর্বপুরুষ)শুধু z-index দিলে
fixedনাviewportসবসময়
stickyহ্যাঁনিকটতম scroll container-এর দৃশ্যমান অংশসবসময়

একটি শব্দ মনে রাখুন: যার position static ছাড়া অন্য যেকোনো মান, তাকে বলে positioned element. শুধু positioned element-এর ক্ষেত্রেই top, right, bottom, left আর z-index কাজ করে (একটি ব্যতিক্রম — flex আর grid item-এ z-index position ছাড়াও কাজ করে, পর্ব ২৭-এ grid-এ দেখবেন)।

১৯৯০-এর দশকের মাঝামাঝি ওয়েব পাতা ছিল প্রায় শুধু লেখার সারি। Netscape আর Microsoft দুই কোম্পানিই চাইছিল পাতার জিনিস ইচ্ছামতো জায়গায় “ভাসাতে” — Netscape 4 তো <layer> নামে আলাদা একটি ট্যাগই চালু করেছিল, যা কখনো মানদণ্ডে ঢোকেনি। শেষ পর্যন্ত দুই কোম্পানির প্রস্তাব মিলে W3C-তে “CSS Positioning” নামে খসড়া তৈরি হয়, আর ১৯৯৮ সালের CSS2-তে static, relative, absolute, fixed আর z-index মানদণ্ডে জায়গা পায়। sticky এসেছে অনেক পরে — সে গল্প ভাবনার প্রশ্নে।

২ · relative — জায়গা রেখে একটু সরে বসা

position: relative সবচেয়ে নিরীহ মান। এটি দিলে element normal flow-এই থাকে, ঠিক আগের মতো জায়গা নেয়। তারপর top, right, bottom, left দিলে ব্রাউজার বাক্সটিকে আঁকার সময় সেই দূরত্বে সরিয়ে দেয়। আশপাশের কেউ এই সরে যাওয়া টের পায় না — তাদের হিসাবে বাক্সটি এখনো আসল জায়গাতেই আছে।

দিকগুলো নিয়ে একটু সাবধান। top: 20px মানে “ওপরের দিক থেকে 20px দূরে সরাও” — অর্থাৎ বাক্স নিচে নামে। left: 30px মানে বাঁ দিক থেকে 30px দূরে — বাক্স ডানে যায়। ঋণাত্মক মান দিলে উল্টো দিকে। নিচের এডিটরে দেখুন:

.row { padding: 20px; background: #f1f5f9; }
.box {
  display: inline-block;
  width: 70px; height: 70px;
  margin-right: 10px;
  line-height: 70px; text-align: center;
  font: 700 24px sans-serif;
  background: #93c5fd; border: 2px solid #1d4ed8;
}
.move {
  position: relative;
  top: 20px;    /* ওপর থেকে 20px দূরে → নিচে নামে */
  left: 30px;   /* বাঁ থেকে 30px দূরে → ডানে যায় */
  background: #fca5a5;
}

লাল “২” নিচে আর ডানে সরে গিয়ে “৩”-এর ওপর আংশিক উঠে গেছে, কিন্তু “৩” একচুলও সরেনি — “২”-এর আসল জায়গাটা ফাঁকা রয়ে গেছে, আর নিচের লাইনটিও যেখানে ছিল সেখানেই। খেয়াল করুন, “২” আঁকা হয়েছে “৩”-এর ওপরে; positioned element সাধারণ বাক্সের পরে আঁকা হয় (অংশ ৬-এ এর কারণ)। চেষ্টা করুন: top-কে -20px করুন — বাক্স ওপরে উঠবে। তারপর position: relative; লাইনটি মুছে দিন — top আর left থাকা সত্ত্বেও কিছুই হবে না, কারণ static বাক্সে এগুলো কাজ করে না।

বাস্তবে relative দিয়ে বাক্স সরানো কমই দরকার হয় — যেমন আইকনকে লেখার সাথে মেলাতে ১-২ পিক্সেল নামানো। relative-এর আসল কাজ অন্যটি, আর সেটিই পরের অংশের চাবি: একটি relative বাক্স তার ভেতরের absolute বাক্সগুলোর containing block হয়ে যায়। নিজে না সরেও সে বলে দেয়, “আমার ভেতরের ভাসমান বাক্সগুলো আমার কিনারা থেকে মাপ নেবে।” (relative-এ top আর bottom একসাথে দিলে top জেতে; বাক্সের মাপ কখনো বদলায় না।)

৩ · absolute — flow থেকে বেরিয়ে containing block ধরে বসা

position: absolute দিলে দুটি বড় ঘটনা ঘটে। প্রথমত, বাক্সটি normal flow থেকে পুরোপুরি বেরিয়ে যায় — পাশের আর পরের element-রা এমন আচরণ করে যেন সে নেই, তার জায়গা দখল করে নেয়। দ্বিতীয়ত, top/right/bottom/left এখন মাপা হয় একটি নির্দিষ্ট আয়তক্ষেত্রের কিনারা থেকে, যার নাম containing block.

প্রশ্ন হলো, সেই containing block কে? নিয়মটি এক লাইনের: DOM গাছে ওপরের দিকে উঠতে থাকুন; প্রথম যে পূর্বপুরুষের position static নয়, সে-ই containing block (ঠিক করে বললে, তার padding-এর বাইরের কিনারা, border-এর ঠিক ভেতর)। কাউকে না পেলে ব্রাউজার ব্যবহার করে initial containing block — পাতার একেবারে শুরুতে বসানো, viewport-এর সমান একটি আয়তক্ষেত্র। নিচের ডায়াগ্রামে ব্রাউজারের এই খোঁজাখুঁজি ধাপে ধাপে দেখুন:

1<body>2  <main class="shop">      ← relative3    <article class="card">  ← static4      <p class="price">     ← static5        ৳ ৮,৫০০6        <span class="badge">  ← absolute7      </p>8    </article>9  </main>10</body>
ব্রাউজারের খোঁজ
যাচাই
position
ফল
  1. শুরু: ব্যাজটি absolute. .badge-এ position: absolute; top: 0; right: 0; লেখা। ব্রাউজারের প্রশ্ন — “0 কোথা থেকে মাপব?” সে parent থেকে শুরু করে ওপরে ওঠে।
  2. parent p.price — static. এর position কিছু দেওয়া নেই, অর্থাৎ static. Static বাক্স containing block হতে পারে না, তাই বাদ। আরও এক ধাপ ওপরে।
  3. article.card — এটিও static. নতুনরা ধরে নেন, “ব্যাজ তো কার্ডের ভেতরে, কার্ডের কোণেই বসবে।” কিন্তু কার্ডে position নেই — তাই এটিও বাদ।
  4. main.shop — relative! খোঁজা শেষ। প্রথম positioned পূর্বপুরুষ পাওয়া গেছে। ব্যাজ তাই কার্ডের কোণে নয়, main-এর ওপর-ডান কোণে বসবে। সমাধান? কার্ডেই position: relative দিন — তখন ধাপ ৩-এই খোঁজা থামবে।
  5. যদি কাউকেই না পাওয়া যেত? ধরুন main-এর relative-ও মুছে দিলেন। তখন body, html — কেউ positioned নয়। ব্রাউজার তখন initial containing block (ICB) নেয়: পাতার একেবারে শুরুতে viewport-এর সমান মাপের আয়তক্ষেত্র। ব্যাজ পাতার ওপর-ডান কোণে বসবে, আর পাতা স্ক্রল করলে পাতার সাথেই ওপরে উঠে যাবে — fixed-এর মতো আটকে থাকবে না।
  6. একটি লুকানো নিয়ম — transform-ও containing block বানায়। position ছাড়াও কিছু property একটি বাক্সকে containing block বানিয়ে দেয়: transform, filter, perspective, backdrop-filter, contain: layout বা contain: paint, আর এগুলোর জন্য will-change. কার্ডে hover-এ একটু বড় হওয়ার জন্য transform থাকলে কার্ড static হলেও ব্যাজ কার্ডেই আটকাবে। পরের অংশে দেখবেন, fixed-এর বেলায় এই নিয়মই বড় ফাঁদ।

এবার সবচেয়ে প্রচলিত ব্যবহারটি নিজে চালিয়ে দেখুন — পণ্য-কার্ডের কোণে ছাড়ের ব্যাজ। কার্ডে position: relative (containing block বানানোর জন্য), ব্যাজে position: absolute:

.card {
  position: relative;   /* ← ব্যাজের containing block */
  width: 200px;
  margin: 30px;
  padding: 16px;
  border: 1px solid #cbd5e1;
  border-radius: 12px;
  font-family: sans-serif;
}
.badge {
  position: absolute;
  top: -10px;
  right: -10px;
  padding: 4px 10px;
  border-radius: 999px;
  background: #dc2626; color: #fff;
  font-weight: 700;
}

লাল ব্যাজটি কার্ডের ওপর-ডান কোণে, অর্ধেকটা কার্ডের বাইরে (ঋণাত্মক মানের জন্য)। আর HTML-এ ব্যাজ শিরোনামের আগে লেখা হলেও “জামদানি শাড়ি” একেবারে ওপর থেকেই শুরু হয়েছে — কারণ ব্যাজ flow থেকে বেরিয়ে গেছে, কোনো জায়গা নিচ্ছে না। চেষ্টা করুন: .card থেকে position: relative; লাইনটি মুছুন — ব্যাজ লাফিয়ে প্রিভিউর ওপর-ডান কোণে চলে যাবে (initial containing block). তারপর ফিরিয়ে এনে right-এর বদলে left: -10px লিখে দেখুন।

absolute বাক্সের মাপ আর inset shorthand

absolute দিলে <span>-এর মতো inline element-ও block-এর মতো আচরণ করে (width, height মানে)। চওড়া না দিলে বাক্স আর পুরো লাইন জুড়ে ছড়ায় না, ভেতরের লেখা যতটুকু, ততটুকুই চওড়া হয় — একে বলে shrink-to-fit. আর উল্টো দুই দিক একসাথে দিলে (যেমন left: 0 আর right: 0) বাক্স দুই কিনারা পর্যন্ত টেনে লম্বা হয়।

চার দিক বারবার লেখার বদলে একটি shorthand আছে — inset. margin-এর মতোই ওপর থেকে ঘড়ির কাঁটার দিকে: ওপর, ডান, নিচ, বাঁ। inset এখন Baseline Widely available — সব আধুনিক ব্রাউজারে নিশ্চিন্তে চলে।

.a { inset: 0; }               /* top:0; right:0; bottom:0; left:0 — পুরোটা ঢেকে দাও */
.b { inset: 10px 20px; }       /* ওপর-নিচ 10px, ডান-বাঁ 20px */
.c { inset: auto 0 0 auto; }   /* নিচ-ডান কোণে: bottom:0; right:0 */
.d { inset-inline-end: 0; }    /* logical রূপ — বাংলায় ডান, আরবিতে বাঁ */

absolute বাক্স মাঝখানে বসানোর দুই উপায়

modal বাক্স বা loading চিহ্ন ঠিক মাঝখানে বসানো একটি ক্লাসিক কাজ। দুটি নির্ভরযোগ্য উপায় আছে:

উপায় ১ · inset: 0 + margin: auto

বাক্সকে চার দিকের কিনারা পর্যন্ত টানুন, তারপর নির্দিষ্ট width আর height দিন। বাকি জায়গা চার দিকের auto margin সমান ভাগ করে নেয় — বাক্স একদম মাঝখানে। মাপ জানা থাকলে সবচেয়ে পরিষ্কার উপায়।

মাপ জানা থাকলে

উপায় ২ · 50% + translate

top: 50%; left: 50% দিলে বাক্সের ওপর-বাঁ কোণ মাঝখানে আসে। তারপর translate: -50% -50% বাক্সকে তার নিজের অর্ধেক মাপ পিছিয়ে দেয়। মাপ না জানলেও কাজ করে।

মাপ অজানা হলে
.screen {
  position: relative;          /* overlay-এর containing block */
  height: 240px;
  padding: 12px;
  background: #e2e8f0;
  font-family: sans-serif;
}
.overlay {
  position: absolute;
  inset: 0;                    /* পুরো .screen ঢেকে দেয় */
  background: rgb(15 23 42 / 0.55);
}
.box {
  position: absolute;
  inset: 0;
  margin: auto;                /* উপায় ১: চার দিকে auto */
  width: 220px; height: 70px;
  line-height: 70px; text-align: center;
  background: #fff; border-radius: 10px;
}

ধূসর পাতার ওপর আধা-স্বচ্ছ কালো আবরণ (overlay), আর তার ঠিক মাঝখানে সাদা বাক্স। দুটিই একই containing block — .screen — ব্যবহার করছে। চেষ্টা করুন: .box-এর inset: 0; আর margin: auto; মুছে লিখুন top: 50%; left: 50%; translate: -50% -50%; — ফল একই। তারপর translate লাইনটিও মুছে দেখুন, বাক্সের ওপর-বাঁ কোণ মাঝখানে এসে বাক্স ডান-নিচে হেলে গেল।

absolute দিয়ে পুরো পাতার লেআউট বানাবেন না — যেমন সাইডবার left: 0, লেখা left: 250px, ফুটার top: 900px. লেখা একটু লম্বা হলে বা ফোনে খুললে সব একে অপরের ওপর উঠে যায়, কারণ absolute বাক্স কেউ কারও খবর রাখে না। পাতার কাঠামোর জন্য Flexbox আর Grid (পর্ব ২৫–২৯); absolute শুধু ছোট “ভাসমান” অংশের জন্য।

৪ · fixed — জানালার কাচে সাঁটা স্টিকার

position: fixed দেখতে absolute-এর মতোই — flow থেকে বেরিয়ে যায়, জায়গা ছেড়ে দেয়। পার্থক্য একটাই, কিন্তু বড়: এর containing block সাধারণত viewport, অর্থাৎ ব্রাউজারের দৃশ্যমান জানালা। পাতা যত খুশি স্ক্রল করুন, fixed বাক্স জানালার একই জায়গায় থাকে। মোবাইলের দোকান-অ্যাপের নিচে “কার্টে যান” বার, চ্যাট-সাহায্যের গোল বোতাম, বা কুকি-নোটিশ — সবই এর উদাহরণ।

নিচের প্রিভিউটি ইচ্ছা করে লম্বা রাখা হয়েছে, তাই প্রিভিউর ভেতরে স্ক্রল করা যাবে। মাউস প্রিভিউর ওপর রেখে চাকা ঘোরান (ফোনে আঙুল দিয়ে টানুন):

<style>
  body { margin: 0; font-family: sans-serif; }
  .page { padding: 16px 16px 70px; }
  .item { height: 110px; margin-bottom: 12px; padding: 10px;
          background: #f1f5f9; border-radius: 8px; }
  .bar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    padding: 12px; text-align: center;
    background: #15803d; color: #fff;
  }
</style>
<div class="page">
  <div class="item">পণ্য ১ — প্রিভিউর ভেতরে নিচে স্ক্রল করুন ↓</div>
  <div class="item">পণ্য ২</div>
  <div class="item">পণ্য ৩</div>
  <div class="item">পণ্য ৪</div>
  <div class="item">পণ্য ৫</div>
  <div class="item">পণ্য ৬ — তালিকা শেষ</div>
  <div class="bar">কার্টে ৩টি পণ্য · ৳ ১,২৫০ — চেকআউট</div>
</div>

সবুজ “কার্টে ৩টি পণ্য” বারটি প্রিভিউর নিচে আটকে আছে; ভেতরে স্ক্রল করলে পণ্যগুলো ওপরে উঠে যায়, বার নড়ে না। (প্রিভিউ নিজেই একটি ছোট ব্রাউজার-জানালা, তাই এখানে viewport মানে প্রিভিউর জানালা।) .page-এর নিচের 70px padding খেয়াল করুন — না দিলে শেষ পণ্যটি বারের নিচে চাপা পড়ত, কারণ fixed বার কোনো জায়গা নেয় না। এবার ফাঁদটি দেখুন: .page-এ transform: translateX(0); যোগ করুন। এটি দেখতে কিছুই বদলায় না, অথচ বার জানালা ছেড়ে চলে যাবে তালিকার একেবারে শেষে — স্ক্রল করে নিচে গেলে তবেই দেখবেন। transform-এর বদলে filter: blur(0); দিয়েও একই ঘটনা ঘটে।

fixed-এর সবচেয়ে বড় ফাঁদ: কোনো পূর্বপুরুষে transform, filter, perspective, backdrop-filter, contain: paint (বা এগুলোর will-change) থাকলে সেই পূর্বপুরুষই fixed বাক্সের containing block হয়ে যায় — viewport আর নয়। ফলে fixed বাক্স হঠাৎ স্ক্রলের সাথে চলতে শুরু করে। একটি অ্যানিমেশন লাইব্রেরি বা “glass” ইফেক্টের backdrop-filter পাতার মোড়কে বসালেই পুরো সাইটের fixed header ভেঙে যেতে পারে। সমাধান: modal, toast বা fixed বারকে এমন জায়গায় রাখুন যার কোনো পূর্বপুরুষে এসব নেই — সাধারণত <body>-এর সরাসরি child হিসেবে — অথবা আধুনিক top layer ব্যবহার করুন (অংশ ৭)।

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

৫ · sticky — সীমায় পৌঁছে আটকে যাওয়া

position: sticky দুই জগতের মিশ্রণ। শুরুতে এটি relative-এর মতো — flow-এ থাকে, নিজের জায়গা নেয়। কিন্তু স্ক্রল করতে করতে যখন বাক্সটি একটি সীমা ছুঁয়ে ফেলে — যেমন top: 0 মানে “দৃশ্যমান অংশের ওপর থেকে 0 দূরত্ব” — তখন সেখানেই আটকে যায়, অনেকটা fixed-এর মতো। আর যখন তার parent-এর নিচের প্রান্ত উঠে আসে, তখন parent-ই তাকে ঠেলে নিয়ে বেরিয়ে যায়। এটি Baseline Widely available; পুরোনো -webkit-sticky prefix আর লাগে না।

ভূমিকা — আমাদের দোকানের ক্রেতা-তালিকা
নাম অনুযায়ী সাজানো…
ক — শিরোনাম (sticky)
কামাল
করিম
ক — শিরোনাম · top: 0 ছুঁয়েছে
কামাল
করিম
কাকলী
কুদ্দুস
ক — শিরোনাম · আটকে আছে
কুদ্দুস
কোহিনূর
খালেদ
↑ সারিগুলো শিরোনামের নিচ দিয়ে চলে যাচ্ছে
ক — শিরোনাম · ঠেলে বের হচ্ছে
খালেদ — ক-অংশের শেষ
র — পরের শিরোনাম
রহিম
রিনা
  1. স্বাভাবিক অবস্থা। লাল দাগটি হলো সীমা — দৃশ্যমান অংশের ওপর থেকে top: 0 দূরত্ব। হলুদ শিরোনাম এখনো দাগের নিচে, তাই সে সাধারণ বাক্সের মতো flow-এ নিজের জায়গায় আছে। সবুজ অংশটি তার parent.
  2. সীমা ছুঁয়ে ফেলল। স্ক্রল করতে করতে শিরোনামের ওপরের কিনারা লাল দাগে পৌঁছাল। সাধারণ বাক্স হলে এখন ওপরে বেরিয়ে যেত; sticky বলে এখানেই থামল।
  3. আটকে আছে। আরও স্ক্রল করলেও শিরোনাম দাগে লেগে থাকে, আর নামের সারিগুলো তার নিচ দিয়ে ওপরে চলে যায়। এজন্যই sticky শিরোনামে একটি ব্যাকগ্রাউন্ড রং দিতে হয় — না হলে নিচ দিয়ে যাওয়া লেখা স্বচ্ছ শিরোনামের ভেতর দিয়ে দেখা যায়।
  4. parent শেষ — ঠেলে বের করা। sticky বাক্স কখনো নিজের parent-এর বাইরে যায় না। সবুজ “ক-অংশ” শেষ হয়ে আসতেই তার নিচের কিনারা শিরোনামকে ঠেলে ওপরে তুলে দিল, আর নিচ থেকে পরের অংশের “র” শিরোনাম এসে একই জায়গায় আটকাবে। মোবাইলের contact তালিকায় অক্ষরের শিরোনাম ঠিক এভাবে বদলায়।

এবার নিজে স্ক্রল করে দেখুন। এখানে তালিকাটি একটি নির্দিষ্ট উচ্চতার বাক্সে রাখা হয়েছে, আর overflow-y: auto দিয়ে বাক্সটিকে নিজের ভেতরে স্ক্রল করার ক্ষমতা দেওয়া হয়েছে — অর্থাৎ বাক্সটি একটি scroll container. sticky শিরোনাম এই বাক্সের দৃশ্যমান অংশের সাপেক্ষেই আটকাবে:

.list {
  height: 200px;
  overflow-y: auto;      /* নিজের স্ক্রল — scroll container */
  border: 2px solid #334155;
  font-family: sans-serif;
}
h4 {
  position: sticky;
  top: 0;                /* সীমা — এটি ছাড়া sticky কাজ করে না */
  margin: 0;
  padding: 6px 10px;
  background: #fde68a;   /* রং না দিলে নিচের লেখা দেখা যাবে */
}
p { margin: 0; padding: 10px; border-bottom: 1px solid #e2e8f0; }

বাক্সের ভেতরে স্ক্রল করুন: হলুদ “ক” ওপরে আটকে থাকে, তারপর “ক”-এর section শেষ হতেই “র” এসে তাকে ঠেলে সরিয়ে দেয়। চেষ্টা করুন: top: 0; লাইনটি মুছে দিন — sticky থাকা সত্ত্বেও শিরোনাম আর আটকাবে না, কারণ কোথায় আটকাবে সেই সীমাই নেই। তারপর top-কে 20px করুন — এবার ওপর থেকে একটু নিচে আটকাবে। শেষে background মুছে দেখুন, নাম আর শিরোনাম একে অপরের ওপর মিশে যাচ্ছে।

“sticky কাজ করছে না” — চারটি পরিচিত কারণ

কারণকেন হয়সমাধান
সীমা দেওয়া নেইtop, bottom, left বা right-এর অন্তত একটি না দিলে sticky বাক্স relative-এর মতোই আচরণ করেtop: 0 (বা দরকারি দিক) দিন
কোনো পূর্বপুরুষে overflow: hidden, auto বা scrollsticky আটকায় নিকটতম scroll container-এ। overflow: hidden-ও একটি scroll container বানায় — যেটি নিজে কখনো স্ক্রল হয় না, তাই sticky-ও কখনো আটকায় নাসেই overflow সরান, বা কাটার দরকার হলে overflow: clip দিন — এটি scroll container বানায় না
parent-এর উচ্চতা বাক্সের সমানsticky শুধু parent-এর ভেতরে চলাফেরা করে; parent যদি ঠিক বাক্সের সমান উঁচু হয়, নড়ার জায়গাই নেইsticky element-কে বড় parent-এর (যেমন পুরো section বা পাতার) সরাসরি child বানান
flex বা grid-এর ভেতরে টেনে লম্বা হওয়াflex/grid item ডিফল্টে পুরো সারির উচ্চতায় টানা হয় — সাইডবার parent-এর সমান লম্বা, তাই আটকানোর কিছু নেইitem-এ align-self: start দিন (পর্ব ২৫)

DevTools-এ পূর্বপুরুষদের Computed স্টাইলে overflow দেখুন — visible ছাড়া কিছু পেলেই সন্দেহভাজন পেয়ে গেলেন। overflow: clip এখন Baseline Widely available.

sticky টেবিল-শিরোনাম

লম্বা টেবিলে — যেমন পরীক্ষার ফলে — স্ক্রল করতে করতে কোন কলাম কিসের, ভুলে যাওয়া সহজ। th { position: sticky; top: 0; } দিলেই কলামের শিরোনাম ওপরে আটকে থাকে। নির্ভরযোগ্যতার জন্য thead বা tr-এ নয়, প্রতিটি th-এ sticky দিন। আর একটি সূক্ষ্ম ব্যাপার: টেবিলে border-collapse: collapse থাকলে border-গুলো পাশের ঘরগুলোর সাথে ভাগাভাগি হয়, তাই অনেক ব্রাউজারে sticky শিরোনামের border ঠিকমতো সাথে ওঠে না — নিরাপদ থাকতে border-collapse: separate; border-spacing: 0; ব্যবহার করুন। অনুশীলন ৩-এ এটি নিজে বানাবেন।

৬ · z-index আর stacking context — কে কার ওপরে

বাক্সগুলো একে অপরের ওপর পড়লে কোনটি সামনে দেখা যাবে, তার জন্য ব্রাউজার একটি তৃতীয় অক্ষ কল্পনা করে — পর্দা থেকে আপনার চোখের দিকে। এই অক্ষে ক্রম ঠিক করে z-index. বড় সংখ্যা মানে সামনে। z-index শুধু positioned element (আর flex/grid item)-এ কাজ করে; static বাক্সে লিখলে কিছুই হয় না।

কিন্তু z-index একটি বিশাল, সবার জন্য খোলা প্রতিযোগিতা নয়। পাতার element-রা কয়েকটি দলে ভাগ হয়ে থাকে, প্রতিটি দলের নাম stacking context. z-index শুধু একই দলের ভেতরে তুলনা হয়। একটি দল পুরোটা একসাথে ওপরে বা নিচে থাকে — তার ভেতরের কোনো সদস্য যত বড় সংখ্যাই পাক, দলের বাইরের কাউকে টপকাতে পারে না।

একটি আবাসিক ভবনের কথা ভাবুন। দোতলার কোনো ফ্ল্যাটে আপনি যত তাক-ই ওপরে ওপরে বসান, সবই দোতলার ভেতরে — তিনতলার মেঝে টপকাবে না। ফ্ল্যাট হলো stacking context, তাকের ক্রম হলো ভেতরের z-index, আর তলার নম্বর হলো ফ্ল্যাটের নিজের z-index. দোতলার ৯৯৯৯ নম্বর তাক-ও তিনতলার নিচেই।

এবার একটি অতি পরিচিত বাগ ধাপে ধাপে ভেঙে দেখি। দোকানের header-এ একটি dropdown মেনু আছে, যাকে z-index: 9999 দেওয়া হয়েছে। তবু নিচের অফার-ব্যানার মেনুটিকে ঢেকে দিচ্ছে:

1.header { position: relative; z-index: 1; }2.menu   { position: absolute; z-index: 9999; }3.banner { position: relative; z-index: 2; }45<header class="header">6  <ul class="menu">…</ul>7</header>8<div class="banner">…</div>
root স্তরে তুলনা
header
menu
banner
  1. শুরু — একটিই দল। পাতার root element (html) সবসময় একটি stacking context. আপাতত header আর banner দুজনই এই root দলের সদস্য।
  2. header-এ position: relative; z-index: 1. positioned element-এ auto ছাড়া অন্য কোনো z-index দিলে সে নিজেই একটি নতুন stacking context হয়ে যায়। header এখন root দলে “1” নম্বর নিয়ে আছে, আর নিজের ভেতরে একটি আলাদা দল খুলেছে।
  3. menu-র z-index: 9999 — কিন্তু কোন দলে? menu header-এর ভেতরে, তাই তার 9999 তুলনা হবে শুধু header-এর ভেতরের সদস্যদের সাথে। root-এর চোখে সে “header দলের” অংশ মাত্র।
  4. banner-এ z-index: 2. banner header-এর ভেতরে নয়, সরাসরি root দলে। তাই root দলে এখন দুজন প্রতিযোগী: header (1) আর banner (2).
  5. root দলে তুলনা — banner জিতল। 2 > 1, তাই banner header-এর পুরো দলের ওপরে আঁকা হলো, আর menu দলের সাথেই নিচে পড়ল। 9999 কোনো কাজে আসেনি।
  6. সমাধান — দল ভেঙে দিন বা দলকে ওপরে তুলুন। header-এর z-index: 1 মুছে দিলে header আর দল থাকে না; menu সরাসরি root দলে যায় এবং 9999 দিয়ে banner-কে হারায়। অথবা header-কেই z-index: 3 দিন — পুরো দল banner-এর ওপরে উঠবে। দুটিতেই আসল প্রশ্ন একটাই: “কোন দলে তুলনা হচ্ছে?”

এবার বাগটি নিজের চোখে দেখুন, তারপর নিজেই সারান:

body { font-family: sans-serif; }
.header {
  position: relative;
  z-index: 1;            /* ← এখানেই নতুন stacking context */
  padding: 10px;
  background: #1e3a8a; color: #fff;
}
.menu {
  position: absolute;
  top: 100%; left: 10px;
  z-index: 9999;         /* এত বড় সংখ্যা — তবু নিচে! */
  margin: 0; padding: 8px 28px;
  background: #fff; color: #111;
  border: 1px solid #94a3b8;
}
.banner {
  position: relative;
  z-index: 2;
  margin-top: 8px; padding: 30px 10px;
  background: #fbbf24;
}

নীল header-এর নিচে সাদা মেনুর শুধু একটি সরু ফালি দেখা যাচ্ছে; বাকি পুরো মেনু — “চাল”, “ডাল”, “তেল” — হলুদ ব্যানারের নিচে চাপা। চেষ্টা করুন: .header-এর z-index: 1; লাইনটি মুছে দিন — মেনু পুরোপুরি ব্যানারের ওপরে চলে আসবে। আবার ফিরিয়ে এনে এবার মানটা 3 করুন — তাতেও ঠিক হয়। শেষে .menu-র z-index-কে 1 করে দিন: ফল বদলায় কি? (বদলায় না — ভেতরের সংখ্যা এখানে অপ্রাসঙ্গিক।)

কী কী নতুন stacking context বানায়

শুধু z-index নয় — আরও অনেক property চুপচাপ নতুন দল খুলে দেয়। বাগ খুঁজতে এই তালিকা খুব কাজের:

  • পাতার root element (html)
  • position: relative বা absolute + auto ছাড়া যেকোনো z-index
  • position: fixed বা sticky — z-index ছাড়াই, সবসময়
  • flex বা grid item, যার z-index auto নয়
  • opacity 1-এর কম (যেমন opacity: 0.99) — এটিই সবচেয়ে চমকে দেয়
  • transform, translate, scale, rotate, filter, backdrop-filter, perspective, clip-path, mask — none ছাড়া যেকোনো মান
  • mix-blend-mode normal ছাড়া অন্য কিছু; contain: layout বা paint; এসব property-র জন্য will-change
  • isolation: isolate — যার একমাত্র কাজই নতুন দল খোলা

isolation: isolate — ইচ্ছা করে একটি দল খোলা

কখনো কখনো আপনি নিজেই দল চান। ধরুন একটি কার্ডের ভেতরে সাজানোর জন্য ::before-এ z-index: -1 দিয়ে একটি রঙিন আকৃতি লেখার পেছনে রাখলেন। কার্ড নিজে কোনো দল না হলে সেই -1 চলে যায় কার্ডেরও পেছনে — কার্ডের নিজের ব্যাকগ্রাউন্ডের নিচে — আর আকৃতিটি ঢাকা পড়ে হারিয়ে যায়। কার্ডে z-index দিলে সারানো যায়, কিন্তু তাতে কার্ড আবার পাতার অন্য z-index-এর সাথে প্রতিযোগিতায় নামে। সবচেয়ে পরিষ্কার সমাধান isolation: isolate: এটি কার্ডকে একটি দল বানায়, কিন্তু কোনো z-index নম্বর দেয় না, আর position-ও লাগে না। Baseline Widely available.

.card {
  position: relative;                /* ::before-এর containing block */
  isolation: isolate;                /* কার্ড নিজেই একটি দল */
}
.card::before {
  content: ""; position: absolute; inset: 0;
  z-index: -1;                       /* কার্ডের লেখার পেছনে, কিন্তু কার্ডের ভেতরেই */
  background: linear-gradient(135deg, #fde68a, #fca5a5);
}

একটি দলের ভেতরে আঁকার ক্রম

z-index কিছু না দিলেও একটি নির্দিষ্ট ক্রমে আঁকা হয়, যাকে বলে painting order. একই stacking context-এর ভেতরে পেছন থেকে সামনে:

  1. দলের মূল বাক্সের ব্যাকগ্রাউন্ড আর border
  2. ঋণাত্মক z-index-এর সদস্যরা (ছোট থেকে বড়)
  3. normal flow-এর block বাক্স
  4. float বাক্স
  5. inline লেখা ও inline বাক্স
  6. z-index: auto বা 0-এর positioned সদস্যরা — HTML-এ যে ক্রমে লেখা
  7. ধনাত্মক z-index-এর সদস্যরা (ছোট থেকে বড়)

এজন্যই অংশ ২-এর লাল বাক্স “২” কোনো z-index ছাড়াই “৩”-এর ওপরে উঠেছিল — positioned হওয়ায় সে ধাপ ৬-এ আঁকা হয়েছে, আর “৩” ধাপ ৩/৫-এ। আর দুটি positioned বাক্সের z-index সমান হলে HTML-এ যেটি পরে লেখা, সেটিই সামনে।

আরও গভীরে: stacking context শুধু ক্রমের নিয়ম নয়, ব্রাউজারের আঁকার কৌশলের সাথেও জড়িত। একটি stacking context-কে ব্রাউজার একটি পুরো ইউনিট হিসেবে আঁকে — opacity: 0.5 দিলে পুরো দলটি আগে একসাথে আঁকা হয়, তারপর একবারে আধা-স্বচ্ছ করা হয়। এজন্যই স্পেসিফিকেশনে opacity-কে দল বানাতেই হয়; না হলে ভেতরের সদস্যরা বাইরের কারও সাথে মিশে গেলে “আধা-স্বচ্ছ দল” আঁকাই সম্ভব হতো না। transform-ওয়ালা বা fixed বাক্স অনেক সময় GPU-তে আলাদা স্তরে (layer) রাখা হয়, আর শেষ ধাপে compositing-এ জোড়া লাগে — তাই এগুলো স্ক্রল ও অ্যানিমেশনে মসৃণ। তবে মনে রাখুন, stacking context আর GPU layer এক জিনিস নয়: প্রথমটি স্পেসিফিকেশনের নিয়ম, দ্বিতীয়টি ব্রাউজারের ভেতরের কৌশল, যা ব্রাউজারভেদে আলাদা।

৭ · top layer — z-index-এর বাইরের একটি স্তর

এতক্ষণের নিয়ম মেনে modal বানানো ঝামেলার: fixed overlay, বিশাল z-index, পূর্বপুরুষে transform বা overflow: hidden নেই তা নিশ্চিত করা। আধুনিক HTML এর মৌলিক সমাধান দিয়েছে — top layer.

top layer হলো পুরো পাতার সব stacking context-এর ওপরে ব্রাউজারের নিজস্ব একটি আলাদা স্তর। তিন ধরনের element সেখানে ওঠে: showModal() দিয়ে খোলা <dialog> আর popover attribute-এর element (দুটিই পর্ব ১১-এ দেখেছেন), আর fullscreen অবস্থার element. top layer-এ ওঠা element-এর ওপর কোনো পূর্বপুরুষের z-index, opacity, transform বা overflow কাজ করে না; DOM-এ যেখানেই থাকুক, সবার ওপরে আঁকা হয়, আর containing block হয় viewport. modal-এর পেছনের আবরণও আলাদা করে বানাতে হয় না — ::backdrop pseudo-element দিয়েই স্টাইল করা যায়।

<!-- পুরোনো উপায়: div + fixed + z-index: 10000 + নিজে focus সামলানো -->
<!-- আধুনিক উপায়: -->
<button popovertarget="help">সাহায্য</button>
<div id="help" popover>ডেলিভারি চার্জ ঢাকার ভেতরে ৬০ টাকা।</div>

<dialog id="confirm">অর্ডারটি নিশ্চিত করবেন?</dialog>
<!-- JavaScript-এ: confirm.showModal()  →  top layer + ::backdrop + পেছনের পাতা inert -->

<dialog> Baseline Widely available, আর popover এখন সব প্রধান ব্রাউজারে আছে — Baseline Newly available. modal dialog-এর বাড়তি লাভ হলো অ্যাক্সেসিবিলিটি: পেছনের পাতা inert হয়ে যায় (Tab চেপে সেখানে যাওয়া যায় না), Esc চাপলে বন্ধ হয়। div দিয়ে বানানো modal-এ এসব নিজে লিখতে হয় — আর AI-এর দেওয়া কোডে প্রায়ই বাদ পড়ে।

আগামীর ঝলক — anchor positioning

tooltip বা dropdown-কে বোতামের ঠিক পাশে বসানো পুরোনো সমস্যা: absolute দিলে পূর্বপুরুষের overflow: hidden কেটে ফেলে, আর top layer-এর popover কাটা না পড়লেও বোতামের পাশে বসবে কীভাবে? উত্তর নতুন anchor positioning: বোতামকে একটি নাম দিন (anchor-name), আর popover-কে বলুন “ওই নোঙরের নিচে বসো” — জায়গা না থাকলে ব্রাউজার নিজেই ওপরে সরিয়ে দেবে। এটি Chromium-ভিত্তিক ব্রাউজারে আগে এসেছে, অন্য প্রধান ব্রাউজারেও সাম্প্রতিক সংস্করণে আসছে — Can I use দেখে নিন; লেখার সময় এটি এখনো Baseline Widely available নয়, তাই পুরোনো ব্রাউজারের জন্য বিকল্প রাখতে হয়। বিস্তারিত পর্ব ৩৭-এ।

৮ · বাস্তব নকশা আর অ্যাক্সেসিবিলিটি

এবার পুরো পর্বকে প্রতিদিনের কাজের সাথে মিলিয়ে নিই। নিচের প্রতিটি নকশায় কোন position আর কেন — একবার চোখ বুলিয়ে নিন:

নকশাকাঠামোখেয়াল রাখুন
কার্ডের কোণে ব্যাজকার্ড relative, ব্যাজ absoluteব্যাজের লেখা যেন screen reader-এর কাছেও অর্থবহ হয় (“২০% ছাড়”)
Dropdown মেনুমেনু-আইটেম relative, তালিকা absolute; top: 100%parent-এর overflow: hidden কেটে দেয়; z-index-এর দল; কীবোর্ডে খোলা যায় কি না
Sticky headerheader { position: sticky; top: 0; }ব্যাকগ্রাউন্ড রং; পূর্বপুরুষে overflow নেই; focus ঢাকা পড়ছে কি না
Modal overlayআধুনিক: <dialog> + showModal(); পুরোনো: fixed + inset: 0পেছনের পাতা inert, Esc-এ বন্ধ, বন্ধ হলে focus বোতামে ফেরে
Tooltipবোতামের wrapper relative, tooltip absolute; বা popoverশুধু hover নয়, focus-এও দেখান; Esc-এ লুকানো যায় (WCAG 1.4.13)
“ওপরে যান” বোতামfixed; right: 16px; bottom: 16pxছোট পর্দায় লেখা বা অন্য বোতাম ঢাকছে কি না

sticky header যেন focus ঢেকে না দেয়

Tab চাপলে ব্রাউজার focus পাওয়া element-কে দৃশ্যমান করতে স্ক্রল করে, কিন্তু জানে না যে ওপরে একটি sticky বা fixed header বসে আছে — ফলে লিংকটি প্রায়ই header-এর নিচে চাপা পড়ে। WCAG 2.2-তে এজন্য নতুন একটি success criterion যোগ হয়েছে: 2.4.11 Focus Not Obscured (Minimum), স্তর AA — focus পাওয়া element লেখকের বানানো কোনো কিছুর নিচে পুরোপুরি ঢাকা পড়তে পারবে না। সহজ সমাধান হলো ব্রাউজারকে header-এর উচ্চতা জানিয়ে দেওয়া:

header { position: sticky; top: 0; z-index: 10; background: #fff; }
html   { scroll-padding-top: 4.5rem; }   /* header-এর উচ্চতার সমান বা একটু বেশি */

scroll-padding-top বলে দেয়, “স্ক্রল করে কিছু দেখানোর সময় ওপরের এতটুকু জায়গা বাদ রেখো।” এতে #id লিংকে লাফ দিলেও শিরোনাম header-এর নিচে লুকায় না। এটি Baseline Widely available.

চোখের ক্রম বনাম DOM-এর ক্রম

position দিয়ে আপনি একটি element-কে পর্দার যেকোনো জায়গায় দেখাতে পারেন, কিন্তু তাতে HTML-এর ক্রম বদলায় না। screen reader পড়ে HTML-এর ক্রমে, Tab-ও চলে HTML-এর ক্রমে। HTML-এর শেষে লেখা বোতামকে absolute দিয়ে ওপরে দেখালে কীবোর্ডে তার কাছে পৌঁছাতে হবে সবার শেষে। নিয়ম: অর্থের ক্রম HTML-এ ঠিক রাখুন, position শুধু ছোটখাটো ভাসমান অংশের জন্য।

position অ্যাক্সেসিবিলিটির একটি পরিচিত কৌশলের ভিত্তিও — যে লেখা চোখে দেখানোর দরকার নেই কিন্তু screen reader-কে শোনাতে হবে, তার জন্য visually-hidden class:

.visually-hidden {
  position: absolute;          /* flow থেকে সরাও, জায়গা নেবে না */
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);       /* চোখে অদৃশ্য */
  white-space: nowrap;
}

display: none দিলে screen reader-ও লেখাটি পায় না; এই কৌশলে লেখা পর্দায় অদৃশ্য কিন্তু accessibility tree-তে থাকে (পর্ব ১২)।

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

  • relative আর absolute-এর পার্থক্য? — relative flow-এ জায়গা রেখে নিজের জায়গা থেকে সরে; absolute flow থেকে বেরিয়ে নিকটতম positioned পূর্বপুরুষের সাপেক্ষে বসে।
  • absolute element-এর containing block কে ঠিক করে? — নিকটতম পূর্বপুরুষ, যার position static নয় (বা যার transform/filter-এর মতো property আছে); কেউ না থাকলে initial containing block.
  • fixed আর sticky-র পার্থক্য? — fixed flow ছেড়ে viewport-এ সবসময় আটকে থাকে; sticky flow-এ থাকে এবং নির্ধারিত সীমায় (threshold, যেমন top: 0) পৌঁছালে শুধু parent-এর সীমার ভেতরে আটকায়।
  • z-index: 9999 দিয়েও element ওপরে আসছে না কেন? — সে এমন একটি stacking context-এর ভেতরে, যার নিজের ক্রম অন্য দলের চেয়ে নিচে; z-index শুধু একই দলের ভেতরে তুলনা হয়।
  • একটি absolute element-কে মাঝখানে বসাবেন কীভাবে? — inset: 0; margin: auto; সাথে নির্দিষ্ট মাপ, অথবা top: 50%; left: 50%; translate: -50% -50%.

৯ · AI-এর যুগে — position-এর কাজ AI-কে দিলে কী দেখবেন

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

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

AI-এর ভুলআপনি যা দেখবেনঠিক উপায়
সব জায়গায় z-index: 9999 বা 99999পরে কোনো কিছু কিছুর ওপরে আনা যায় না, সংখ্যা বাড়তেই থাকেছোট একটি নির্দিষ্ট স্কেল (যেমন CSS variable-এ 10, 20, 30); component-এ isolation: isolate
div + fixed + z-index দিয়ে modalEsc-এ বন্ধ হয় না, Tab পেছনের পাতায় চলে যায়<dialog> + showModal()
absolute দিয়ে পুরো পাতার লেআউটফোনে বা লম্বা লেখায় সব একে অপরের ওপরFlexbox/Grid; absolute শুধু ভাসমান অংশে
sticky দিয়েছে, অথচ parent-এ overflow: hiddenheader আটকায় নাoverflow সরান বা overflow: clip
fixed header, কিন্তু scroll-padding নেইTab বা #id লিংকে লেখা header-এর নিচে চাপাscroll-padding-top
position: -webkit-sticky, -webkit-transformঅপ্রয়োজনীয় বাড়তি লাইনprefix ছাড়া লিখুন

AI-কে কাজ দেওয়ার সময় position-এর শর্তগুলো আগেই লিখে দিন। শেষের অনুরোধটি খেয়াল করুন — প্রতিটি absolute element-এর containing block কে, তা লিখতে বললে AI-এর নিজের ভুলও প্রায়ই বেরিয়ে আসে।

তুমি একজন অভিজ্ঞ ফ্রন্ট-এন্ড ডেভেলপার। আমার অনলাইন দোকানের জন্য HTML আর CSS বানাও: ওপরে একটি sticky header, যার ভেতরে "পণ্য" নামে একটি dropdown মেনু; নিচে পণ্য-কার্ড, প্রতিটির কোণে ছাড়ের ব্যাজ। শর্ত: ১. header-এর জন্য position: sticky ব্যবহার করবে, আর html-এ scroll-padding-top দেবে যাতে focus পাওয়া জিনিস header-এর নিচে না ঢাকে। ২. z-index-এর জন্য :root-এ ৩-৪টি CSS variable-এর ছোট স্কেল বানাবে; 999 বা 9999-এর মতো সংখ্যা দেবে না। ৩. প্রতিটি component-এর মূল বাক্সে দরকার হলে isolation: isolate দেবে। ৪. পাতার লেআউটে absolute ব্যবহার করবে না; শুধু ব্যাজ আর dropdown-এ। ৫. dropdown কীবোর্ডে খোলা-বন্ধ করা যাবে; কোনো -webkit- prefix দেবে না। শেষে একটি তালিকা দাও: প্রতিটি absolute element-এর containing block কোনটি, আর কোন কোন element নতুন stacking context বানাচ্ছে।

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

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

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

প্র ০১একটি নিরীহ transform কেন fixed বাক্সকে “ভেঙে” দেয়? এটা কি ব্রাউজারের বাগ, না ইচ্ছাকৃত নকশা?

এটি বাগ নয় — স্পেসিফিকেশনে লেখা নিয়ম, আর এর পেছনে যুক্তিও আছে। transform একটি element-এর জন্য নতুন একটি স্থানাঙ্ক-ব্যবস্থা (coordinate system) তৈরি করে: element-টি ঘোরানো, বড়-ছোট বা হেলানো হতে পারে, আর তার ভেতরের সবকিছু সেই ঘোরানো জগতের অংশ। ভেতরের fixed বাক্স তখনও viewport-এ আটকে থাকলে parent ৪৫ ডিগ্রি ঘুরলে child কি ঘুরবে, না সোজা থাকবে? দুটো নিয়ম পরস্পরবিরোধী। স্পেসিফিকেশন সিদ্ধান্ত নিয়েছে: transform-ওয়ালা element তার ভেতরের fixed আর absolute — দুই ধরনের বাক্সেরই containing block হবে। ফলে child parent-এর সাথে একসাথে ঘোরে, আর fixed আচরণ হারায়।

ব্রাউজারের ভেতরের কারণও আছে: transform বা filter-ওয়ালা বাক্সকে ব্রাউজার প্রায়ই একটি আলাদা ছবির মতো আঁকে, তারপর পুরো ছবিটা একসাথে ঘোরায় বা ঝাপসা করে। fixed child আলাদা নিয়মে চললে প্রতিটি স্ক্রলে সেই ছবি ভেঙে নতুন করে আঁকতে হতো।

fixed-এর ঝামেলা এর আগেও ছিল। Internet Explorer 6 position: fixed একেবারেই বুঝত না — ডেভেলপাররা JavaScript দিয়ে প্রতিটি স্ক্রলে বাক্স সরাতেন, ফলে বাক্সটি কাঁপত। প্রথম দিকের স্মার্টফোন ব্রাউজারেও fixed ঠিকমতো চলত না, কারণ ফোনে পাতা জুম আর প্যান করা হয় — “জানালা” কোনটা, সেটাই তখন প্রশ্ন।

বাস্তবে এর শিক্ষা হলো: modal, toast বা fixed বার পাতার গভীরে কোনো component-এর ভেতরে না রেখে <body>-এর কাছাকাছি রাখুন। React বা Vue-এর মতো framework-এ এজন্যই “portal” বা “teleport” নামের কৌশল আছে — component কোডে এক জায়গায়, কিন্তু DOM-এ body-র শেষে বসে। আর আধুনিক সমাধান top layer, যা এই সমস্যাটিকেই অপ্রাসঙ্গিক করে দেয়।

মূল উপলব্ধি: CSS-এর অদ্ভুত নিয়মগুলোর বেশিরভাগই আসলে দুটি ইচ্ছার সংঘাত মেটানোর সিদ্ধান্ত। “কেন এমন?” জিজ্ঞেস করলে প্রায় সবসময় একটি যুক্তিসংগত উত্তর মেলে — আর সেটি জানলে বাগ খুঁজতে অনুমান লাগে না।

প্র ০২বড় প্রজেক্টে “z-index যুদ্ধ” কেন বাধে, আর পেশাদার দল কীভাবে তা এড়ায়?

গল্পটি প্রায় সব দলেই এক। একজন ডেভেলপার dropdown-কে ওপরে আনতে z-index: 100 দিলেন। আরেকজন modal বানিয়ে নিশ্চিত হতে দিলেন 1000. তৃতীয়জন একটি চ্যাট-উইজেট যোগ করলেন, যেটি modal-এর নিচে পড়ছিল — দিলেন 9999. কোনো বাইরের স্ক্রিপ্ট হয়তো 2147483647 ব্যবহার করে (৩২-বিটের সবচেয়ে বড় পূর্ণসংখ্যা)। একসময় আর বাড়ানোর জায়গা থাকে না, আর কেউ জানে না কোন সংখ্যা কেন।

এর মূল কারণ দুটি। প্রথমত, মানুষ z-index-কে একটি বৈশ্বিক তালিকা ভাবেন, অথচ এটি stacking context-ভিত্তিক — তাই বড় সংখ্যা কাজ না করলে না বুঝে আরও বড় সংখ্যা দেন। দ্বিতীয়ত, কোনো কেন্দ্রীয় নিয়ম নেই — যে যার মতো সংখ্যা বেছে নেন।

পেশাদার দলগুলোর সমাধান সাধারণত তিন স্তরের:

  • একটি স্কেল: পুরো প্রজেক্টের জন্য অল্প কয়েকটি নাম দেওয়া মান — যেমন --z-dropdown, --z-sticky, --z-modal, --z-toast — একটি ফাইলে, CSS variable বা Sass variable হিসেবে। Bootstrap-এর মতো framework-ও dropdown, sticky, modal, tooltip-এর জন্য আলাদা আলাদা নির্দিষ্ট মান ঠিক করে রাখে, যাতে সবাই একই ভাষায় কথা বলে।
  • component-এ isolation: প্রতিটি component নিজের ভেতরে isolation: isolate দিয়ে ছোট মান (1, 2, -1) ব্যবহার করে। ভেতরের সংখ্যা বাইরে ফাঁস হয় না, তাই বৈশ্বিক স্কেলে শুধু কয়েকটি বড় স্তর লাগে।
  • top layer: modal আর popover-কে <dialog> ও popover-এ সরালে সবচেয়ে বড় প্রতিযোগীরা z-index জগৎ থেকেই বেরিয়ে যায়।

মূল উপলব্ধি: z-index-এর সমস্যা সংখ্যার নয়, কাঠামোর। দল (stacking context) বুঝে ছোট, নামওয়ালা স্কেল ব্যবহার করলে “আরও একটা 9 যোগ করি” প্রবণতা থেকে মুক্তি মেলে।

প্র ০৩position: sticky আসার আগে sticky header কীভাবে বানানো হতো, আর ব্রাউজারে আনতে এত সময় লাগল কেন?

sticky আসার আগে এটি করা হতো JavaScript দিয়ে। পাতার প্রতিটি স্ক্রলে একটি ফাংশন চলত: “header-এর ওপরের কিনারা কি এখন জানালার ওপরে পৌঁছেছে?” পৌঁছালে header-কে position: fixed বানানো হতো, আর তার ফাঁকা জায়গা ভরাতে নিচে একটি সমান উচ্চতার খালি বাক্স (placeholder) বসানো হতো — না হলে নিচের লেখা হঠাৎ লাফিয়ে উঠত। ফিরে এলে উল্টো কাজ। jQuery-র যুগে এর জন্য অসংখ্য plugin ছিল।

সমস্যা ছিল গতি। স্ক্রল ইভেন্ট খুব ঘন ঘন আসে, আর প্রতিবার element-এর মাপ পড়া ব্রাউজারকে লেআউট হিসাব শেষ করতে বাধ্য করে — দুর্বল ফোনে স্ক্রল কাঁপত। তার ওপর আধুনিক ব্রাউজার স্ক্রল চালায় মূল JavaScript thread থেকে আলাদা জায়গায়, ফলে স্ক্রিপ্টের হিসাব প্রায়ই এক ফ্রেম পিছিয়ে পড়ত, header লাফিয়ে আটকাত।

sticky-র ধারণা প্রস্তাব হিসেবে এসেছিল ২০১২ সালের দিকে, আর Apple-এর WebKit-এ -webkit-sticky নামে প্রথম দেখা যায়। Chrome একবার এটি পরীক্ষামূলকভাবে যোগ করেও সরিয়ে নেয় — ব্রাউজারের ভেতরের দ্রুত-স্ক্রলের ব্যবস্থার সাথে এটি মেলানো কঠিন ছিল — পরে ২০১৭ সালে নতুন করে আনে। কঠিন হওয়ার কারণ: sticky-র জায়গা নির্ভর করে স্ক্রলের অবস্থান, scroll container, parent-এর সীমা আর অন্য sticky বাক্সের ওপর, এবং এটি প্রতিটি ফ্রেমে নিখুঁত হতে হয়। ব্রাউজার নিজে এটি করলে স্ক্রলের সাথে একই ধাপে হিসাব হয়, তাই কোনো কাঁপুনি নেই।

“header এখন আটকে আছে কি না” জেনে স্টাইল বদলাতে (যেমন ছায়া দেখানো) আজও সাধারণত JavaScript-এর IntersectionObserver লাগে; CSS-এর নতুন scroll-state container query Chromium-ভিত্তিক ব্রাউজারে সম্প্রতি এসেছে, এখনো Baseline নয়।

মূল উপলব্ধি: যে কাজ ব্রাউজার নিজে ঘোষণামূলকভাবে (declaratively) করতে পারে, তা JavaScript দিয়ে নকল করলে প্রায় সবসময় ধীর আর ভঙ্গুর হয়। CSS-এ একটি লাইন অনেক সময় এক ঝুড়ি স্ক্রিপ্টের চেয়ে নির্ভরযোগ্য।

প্র ০৪পুরো পাতাটা absolute দিয়ে সাজালে তো পিক্সেল ধরে নিখুঁত হয় — তাহলে এটা খারাপ অভ্যাস কেন?

২০০০-এর দশকের শুরুতে Dreamweaver-এর মতো ভিজ্যুয়াল টুলে একটি জনপ্রিয় সুবিধা ছিল “Layers” (পরে নাম হয় AP Div — absolutely positioned div). মাউস দিয়ে বাক্স টেনে যেখানে খুশি বসানো যেত, টুল নিজেই প্রতিটির top/left লিখে দিত। দেখতে ছাপা পত্রিকার মতো নিখুঁত, কিন্তু অত্যন্ত ভঙ্গুর।

কারণ absolute বাক্স কেউ কারও খবর রাখে না। ওয়েব পাতা ছাপা কাগজ নয় — এর মাপ প্রতিনিয়ত বদলায়:

  • লেখার দৈর্ঘ্য: একটি পণ্যের বিবরণ এক লাইন থেকে তিন লাইন হলো, কিন্তু নিচের বাক্স তো top: 300px-এ আটকে — লেখা তার ওপর উঠে গেল। বাংলা লেখা ইংরেজির চেয়ে প্রায়ই উঁচু ও চওড়া হয়, তাই অনুবাদ করলেই ভাঙে।
  • পর্দার মাপ: ১৩৬৬px-এর জন্য বানানো পাতা ৩৬০px ফোনে হয় পাশে স্ক্রল, নয় সব জট পাকানো।
  • ব্যবহারকারীর পছন্দ: দৃষ্টিশক্তি কম এমন কেউ লেখা বড় করলেন বা ২০০% জুম করলেন — বাক্সগুলো বড় হলো, কিন্তু জায়গা বদলাল না, ফলে একে অপরকে ঢেকে দিল। WCAG-এর 1.4.4 (লেখা বড় করা) আর 1.4.10 (Reflow — ছোট চওড়ায় পাশে স্ক্রল ছাড়া পড়া যাওয়া) দুটিই এখানে ভাঙে।
  • ক্রম: absolute দিয়ে চোখের ক্রম আর HTML-এর ক্রম আলাদা হয়ে যায়, ফলে screen reader আর কীবোর্ড ব্যবহারকারী ভিন্ন, বিভ্রান্তিকর একটি পাতা পান।

normal flow, Flexbox আর Grid-এর মূল শক্তি হলো বাক্সগুলো একে অপরের মাপ “জানে” — একজন বড় হলে অন্যরা সরে জায়গা দেয়। তাই পাতার কাঠামো এই ব্যবস্থায়, আর absolute শুধু ছোট ভাসমান অংশে — ব্যাজ, আইকন, dropdown.

মূল উপলব্ধি: ওয়েব ডিজাইনের লক্ষ্য একটি নিখুঁত ছবি নয়, বরং এমন একটি ব্যবস্থা যা অজানা লেখা, অজানা পর্দা আর অজানা ব্যবহারকারীর সাথে মানিয়ে নেয়। পিক্সেল-নিখুঁততা প্রায়ই নমনীয়তার দামে কেনা।

প্র ০৫top layer, popover আর anchor positioning এসে গেলে কি position: absolute আর z-index-এর দিন শেষ?

পুরোপুরি নয় — কিন্তু তাদের সবচেয়ে কঠিন কাজগুলো সত্যিই অন্য হাতে যাচ্ছে। “ভাসমান UI” — modal, dropdown, tooltip, toast — বহু বছর ধরে ওয়েবের সবচেয়ে ঝামেলার অংশ। প্রতিটির একই তিন সমস্যা: (১) সবার ওপরে দেখানো (z-index যুদ্ধ), (২) কোনো পূর্বপুরুষের overflow: hidden বা transform-এর হাত থেকে বাঁচা, (৩) ট্রিগার বোতামের পাশে ঠিকঠাক বসানো, আর জায়গা না থাকলে উল্টো দিকে সরানো। এর জন্য Popper বা Floating UI-এর মতো JavaScript লাইব্রেরি তৈরি হয়েছিল।

নতুন প্ল্যাটফর্ম-ফিচারগুলো তিনটি সমস্যা আলাদা আলাদা করে সমাধান করছে। top layer (<dialog>, popover) সমস্যা ১ আর ২ শেষ করে দেয় — top layer-এ ওঠা element কোনো stacking context বা overflow মানে না। popover সাথে আনে light dismiss (বাইরে ক্লিক বা Esc-এ বন্ধ)। anchor positioning সমস্যা ৩-এর সমাধান — CSS-ই বলে দেয় “এই বোতামের নিচে বসো, জায়গা না থাকলে ওপরে”, কোনো স্ক্রিপ্ট ছাড়া।

তবু position আর z-index থাকছে। ব্যাজ বা কার্ডের সাজসজ্জা top layer-এর জিনিস নয়, পাতার অংশ — এখানে relative + absolute-ই সঠিক হাতিয়ার; sticky-র কোনো বিকল্প নেই; আর anchor positioning নিজেই absolute বা fixed element-এর ওপর কাজ করে — এটি absolute-এর বদলি নয়, তার নতুন ক্ষমতা। পুরোনো ব্রাউজারের জন্য absolute-ভিত্তিক বিকল্পও অনেক দিন লাগবে।

এটি progressive enhancement-এর একটি ভালো উদাহরণ: আগে নির্ভরযোগ্য ভিত্তি (absolute + সঠিক containing block), তারপর যে ব্রাউজার পারে তার জন্য উন্নত রূপ (@supports দিয়ে anchor positioning) — পর্ব ৩৭ আর ৪৮-এ এ নিয়ে আরও।

মূল উপলব্ধি: প্ল্যাটফর্ম যখন কোনো কঠিন কাজ নিজে করতে শেখে, পুরোনো হাতিয়ার হারায় না — শুধু তার কাজের সীমা পরিষ্কার হয়। কোন কাজে কোন হাতিয়ার, সেটা জানাই আসল দক্ষতা।

অনুশীলন

বেশিরভাগ কাজ দেখা আর ভাবার — একটিতে কোড লিখবেন, যার উত্তর লাইভ এডিটরেই চালিয়ে দেখা যাবে।

  1. বাস্তব সাইটে খুঁজে দেখুন: যেকোনো খবর বা অনলাইন দোকানের সাইট খুলে স্ক্রল করুন। যে header ওপরে আটকে থাকে, তার ওপর ডান-ক্লিক করে Inspect করুন। Computed-এ position কী — sticky না fixed? Styles প্যানেলে সেই লাইনের চেকবক্স বন্ধ করলে কী হয়?

    অনেক আধুনিক সাইটে sticky পাবেন, কোথাও কোথাও fixed (প্রায়ই JavaScript-এর সাথে)। চেকবক্স বন্ধ করলে header সাধারণ বাক্স হয়ে স্ক্রলের সাথে ওপরে চলে যায়। fixed হলে আরও একটি জিনিস খেয়াল করবেন: পাতার ওপরের দিকে একটি padding-top বা ফাঁকা বাক্স আছে — কারণ fixed header জায়গা নেয় না, তাই লেখা যাতে তার নিচে চাপা না পড়ে সেজন্য আলাদা জায়গা রাখতে হয়। sticky flow-এ নিজের জায়গা রাখে বলে তার এর দরকার হয় না।

  2. অনুমান করুন: একটি <p>-এর ঠিক পরে একটি <div class="note">, তারপর আরেকটি <p>. note-এ শুধু position: absolute; দেওয়া হলো — কোনো top/left নয়। note কোথায় দেখা যাবে, আর দ্বিতীয় প্যারাগ্রাফ কোথায় যাবে?

    note প্রায় সেই জায়গাতেই দেখা যাবে, যেখানে সে সাধারণভাবে থাকত — প্রথম প্যারাগ্রাফের ঠিক নিচে। কারণ কোনো offset না দিলে absolute বাক্স তার static position-এ বসে: flow-এ থাকলে যেখানে থাকত, সেখানে। কিন্তু সে এখন flow-এর বাইরে, তাই দ্বিতীয় প্যারাগ্রাফ উঠে এসে ঠিক সেই জায়গা নেবে — ফলে note আর দ্বিতীয় প্যারাগ্রাফ একে অপরের ওপর পড়বে। note-এর চওড়াও বদলাবে: আর পুরো লাইন জুড়ে নয়, লেখা যতটুকু ততটুকু (shrink-to-fit). এই আচরণ কাজেরও — যেমন কোনো লেখার পাশে ঠিক সেই উচ্চতায় একটি ছোট চিহ্ন ভাসাতে শুধু position: absolute আর margin-left যথেষ্ট।

  3. কোড লিখুন — sticky টেবিল-শিরোনাম: ৮টি জেলা আর তাদের বিভাগের একটি টেবিল বানান, যা ১৮০px উঁচু একটি বাক্সের ভেতরে স্ক্রল হবে, আর স্ক্রল করলেও কলামের শিরোনাম ওপরে আটকে থাকবে।

    একটি সমাধান — চালিয়ে বাক্সের ভেতরে স্ক্রল করুন:

    <style>
      .wrap { height: 180px; overflow: auto; border: 1px solid #94a3b8; }
      table { width: 100%; border-collapse: separate; border-spacing: 0;
              font-family: sans-serif; }
      th, td { padding: 8px 10px; text-align: left;
               border-bottom: 1px solid #e2e8f0; }
      th { position: sticky; top: 0; background: #1e3a8a; color: #fff; }
    </style>
    <div class="wrap" tabindex="0" role="region" aria-label="জেলার তালিকা">
      <table>
        <thead><tr><th scope="col">জেলা</th><th scope="col">বিভাগ</th></tr></thead>
        <tbody>
          <tr><td>ঢাকা</td><td>ঢাকা</td></tr>
          <tr><td>চট্টগ্রাম</td><td>চট্টগ্রাম</td></tr>
          <tr><td>বগুড়া</td><td>রাজশাহী</td></tr>
          <tr><td>সিলেট</td><td>সিলেট</td></tr>
          <tr><td>খুলনা</td><td>খুলনা</td></tr>
          <tr><td>বরিশাল</td><td>বরিশাল</td></tr>
          <tr><td>দিনাজপুর</td><td>রংপুর</td></tr>
          <tr><td>ময়মনসিংহ</td><td>ময়মনসিংহ</td></tr>
        </tbody>
      </table>
    </div>

    বাক্সের ভেতরে স্ক্রল করলে নীল শিরোনাম-সারি ওপরে আটকে থাকে, জেলার নামগুলো তার নিচ দিয়ে যায়। মূল লাইন একটিই: th { position: sticky; top: 0; }, সাথে ব্যাকগ্রাউন্ড রং। .wrap-এ tabindex="0", role আর aria-label দেওয়া হয়েছে যাতে কীবোর্ড ব্যবহারকারীও বাক্সটিতে এসে তীর-চাবি দিয়ে স্ক্রল করতে পারেন (পর্ব ৭)। চেষ্টা করুন: border-spacing: 0 সহ লাইনটিকে border-collapse: collapse; করে দিন, আর th-এ border-bottom: 3px solid #f59e0b; যোগ করে স্ক্রল করুন — কমলা দাগটি শিরোনামের সাথে উঠছে কি না দেখুন।

  4. AI অডিট: যেকোনো AI-কে বলুন “একটি dropdown navigation menu-র HTML ও CSS দাও।” তারপর দেখুন: z-index কত দেওয়া, কোন বাক্স containing block, parent-এ overflow: hidden আছে কি না, আর মেনুটি শুধু Tab আর Enter দিয়ে খোলা যায় কি না।

    সাধারণত যা পাওয়া যায়:

    • z-index: 999 বা 9999-এর মতো মনগড়া বড় সংখ্যা — অথচ মেনুর parent-এ আগেই z-index থাকায় পরে কোনো ব্যানার যোগ হলে মেনু তার নিচে চলে যায়।
    • মেনু খোলা হয় শুধু :hover দিয়ে — কীবোর্ড বা টাচস্ক্রিনে খোলেই না। অন্তত :focus-within লাগবে, আর ভালো সমাধানে একটি <button aria-expanded> বা popover.
    • nav-এর গোল কোণ ঠিক রাখতে overflow: hidden — যা নিচে ঝুলে পড়া মেনুকে কেটে ফেলে।
    • মেনু-আইটেমে position: relative না দেওয়ায় তালিকা ভুল বাক্সের সাপেক্ষে বসে।

    প্রতিটি সমস্যা DevTools-এ পাঁচ মিনিটে ধরা যায় — আর এই যাচাইটাই আসল দক্ষতা।

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

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

ক্রমএকটি position: absolute element-এর containing block খুঁজতে ব্রাউজার যে ক্রমে চিন্তা করে, সাজান (▲▼ চেপে সরান)

  1. element-টি absolute — তাকে flow থেকে বের করা হলো
  2. নিজের parent থেকে খোঁজা শুরু
  3. parent static আর transform-জাতীয় কিছু নেই — আরও এক ধাপ ওপরে
  4. প্রথম যে পূর্বপুরুষের position static নয় (বা transform আছে), সেখানে থামা
  5. কাউকে না পেলে initial containing block ব্যবহার

একটি বাক্সে position: relative; top: 20px; দেওয়া হলো। কী ঘটবে?

  • বাক্সটি flow থেকে বেরিয়ে যাবে, নিচের বাক্স ওপরে উঠে আসবে
  • বাক্সটি 20px নিচে সরে দেখাবে, কিন্তু আসল জায়গাটা খালি থাকবে
  • বাক্সটি 20px ওপরে উঠে যাবে
  • কিছুই হবে না, কারণ top শুধু absolute-এ কাজ করে

একটি position: absolute element-এর top/left সাধারণত কার সাপেক্ষে মাপা হয়?

  • সবসময় সরাসরি parent element-এর সাপেক্ষে
  • সবসময় ব্রাউজারের জানালার (viewport) সাপেক্ষে
  • নিকটতম পূর্বপুরুষ, যার position static নয়
  • সবসময় body element-এর সাপেক্ষে

header-এ position: relative; z-index: 1, তার ভেতরের dropdown-এ z-index: 9999, আর header-এর পরের ব্যানারে position: relative; z-index: 2. dropdown ব্যানারের নিচে চাপা পড়ছে কেন?

  • header একটি stacking context বানিয়েছে, তাই dropdown-এর 9999 শুধু header-এর ভেতরে তুলনা হয়
  • 9999 খুব বড় সংখ্যা, ব্রাউজার একে উপেক্ষা করে
  • ব্যানার HTML-এ পরে লেখা, তাই সবসময় সামনে থাকে
  • absolute element-এ z-index কাজ করে না

একটি header-এ position: sticky দেওয়া হয়েছে, কিন্তু স্ক্রলে আটকাচ্ছে না। নিচের কোনটি এর কারণ হতে পারে না?

  • top-এর মতো কোনো সীমা দেওয়া হয়নি
  • কোনো পূর্বপুরুষে overflow: hidden আছে
  • header-এর parent ঠিক header-এর সমান উঁচু
  • header-এর display হলো block

position বা z-index ছাড়াই কোনটি একটি element-কে নতুন stacking context বানায়?

  • margin: auto
  • opacity: 0.9
  • color: red
  • display: block

showModal() দিয়ে খোলা একটি <dialog> পাতার z-index: 2147483647 দেওয়া বাক্সের ওপরেও দেখা যায়। কেন?

  • ব্রাউজার dialog-কে আপনা থেকে আরও বড় z-index দেয়
  • dialog সবসময় position: fixed, আর fixed সবার ওপরে থাকে
  • modal dialog top layer-এ ওঠে, যা সব stacking context-এর ওপরে
  • dialog HTML-এ সবার শেষে লেখা হয়েছে

শব্দকোষ

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

মনে রাখুন

  1. ডিফল্ট static; relative জায়গা রেখে সরে, absolute আর fixed জায়গা ছেড়ে দেয়, sticky জায়গা রেখে সীমায় আটকায়।
  2. absolute-এর ভিত্তি containing block — নিকটতম non-static (বা transform-ওয়ালা) পূর্বপুরুষ; তাই কার্ডে position: relative, ব্যাজে absolute.
  3. পূর্বপুরুষের transform/filter fixed-কে ভেঙে দেয়; sticky-র জন্য সীমা (top: 0) লাগে, আর পূর্বপুরুষের overflow তাকে আটকে দেয়।
  4. z-index তুলনা হয় শুধু একই stacking context-এ; opacity, transform, isolation-ও দল বানায় — বড় সংখ্যার বদলে দলের কাঠামো ঠিক করুন।
  5. modal আর popover-এর জন্য top layer (<dialog>, popover); sticky header থাকলে scroll-padding-top দিয়ে focus দৃশ্যমান রাখুন।

পরের পর্বে: position ভাসমান অংশের হাতিয়ার — কিন্তু পাতার আসল সাজানো? এবার Flexbox: এক সারিতে বা এক কলামে জিনিস সাজানো, ফাঁক ভাগ করা আর বহু বছরের পুরোনো “মাঝখানে বসানো”-র সমস্যার সবচেয়ে সহজ সমাধান।

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

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