এই পর্বে যা শিখবেন
- Selector আসলে কী করে — DOM গাছ থেকে element বেছে নেওয়ার পুরো ছবি
- type, class, id, universal selector আর comma দিয়ে grouping — কখন কোনটি, আর class-এর নাম রাখার ভালো অভ্যাস
- সাতটি attribute selector আর case-insensitive
iflag - চারটি combinator, ব্রাউজারের ডান-থেকে-বাঁয়ে মেলানোর কৌশল, আর selector-এর গতি নিয়ে প্রচলিত ভুল ধারণা
- pseudo-class-এর এক ঝলক,
:is()/:where()/:not(), আর AI-এর লেখা selector যাচাইয়ের উপায়
১ · Selector কী করে — DOM গাছ থেকে element বাছাই
স্কুলের মাঠে সমাবেশ চলছে। প্রধান শিক্ষক মাইকে বললেন, “দশম শ্রেণির সবাই হাত তুলবে।” তারপর, “যারা স্কাউট দলের, তারা সামনে আসবে।” শেষে, “শুধু রোল এক মঞ্চে আসবে।” খেয়াল করুন, প্রতিবার নির্দেশের প্রথম অংশটি ঠিক করে দিচ্ছে কারা শুনবে, আর দ্বিতীয় অংশ বলছে কী করবে।
CSS ঠিক এভাবেই কাজ করে। পর্ব ১৬-এ আপনি দেখেছেন, প্রতিটি CSS rule-এর দুটি ভাগ — বাঁয়ে selector, তারপর বাঁকা বন্ধনীর ভেতরে এক বা একাধিক declaration. Selector হলো সেই মাইকের ঘোষণার প্রথম অংশ: “এই এই element-এর জন্য…” Declaration হলো কাজের নির্দেশ: “এদের রং হবে সবুজ।”
.sale .price { color: green; font-weight: bold; }
└─ selector ─┘ └──────── declarations ───────┘
কিন্তু ব্রাউজার element খোঁজে কোথায়? আপনার HTML ফাইলে নয় — ফাইল পড়ে ব্রাউজার মেমরিতে যে গাছ বানায়, সেই DOM-এ (পর্ব ৩-এ দেখেছেন)। গাছের প্রতিটি শাখা একটি element. Selector গাছের ওপর একটি ছাঁকনির মতো কাজ করে: যে element শর্ত মানে, সে ধরা পড়ে; বাকিরা ছাঁকনি দিয়ে বেরিয়ে যায়। নিচে একটি অনলাইন দোকানের অফার-তালিকার DOM গাছ। “পরে” চাপুন — প্রতিটি ধাপে একটি selector বদলাবে, আর সবুজ দাগে দেখবেন কোন কোন element ধরা পড়ল।
1<main>2 <h2>আজকের অফার3 <ul class="products">4 <li class="card sale" id="featured">5 <h3>ইলিশ মাছ6 <p class="price">৳ ১,২০০7 <li class="card">8 <h3>সুন্দরবনের মধু9 <p class="price">৳ ৮৫০10 <li class="card sale" data-stock="0">11 <h3>আমের আচার12 <p class="price">৳ ৩২০13 <p class="note">স্টকে নেই14 <p>ঢাকার ভেতরে ডেলিভারি ৬০ টাকা
| selector | |
| ধরন | |
| ধরা পড়ল |
li— ট্যাগের নাম দিয়ে। সবচেয়ে সরল selector: যত<li>আছে, সবাই ধরা পড়ল — লাইন ৪, ৭ আর ১০. মাইকে “দশম শ্রেণির সবাই” বলার মতো।.sale— class দিয়ে। সামনের বিন্দুটি মানে “class”. যাদেরclassattribute-এsaleশব্দটি আছে, তারাই এলো — ইলিশ আর আচার। মধুর কার্ডে শুধুcard, তাই বাদ।#featured— id দিয়ে।#মানে id. পাতায় একটি id একবারই থাকার কথা, তাই এক জনই ধরা পড়ল। “শুধু রোল এক।”p— আবার ট্যাগের নাম। পাঁচটি p ধরা পড়ল — তিনটি দাম, “স্টকে নেই” আর একেবারে নিচের ডেলিভারির লাইনটিও। Selector জানে না আপনি কোনটি চেয়েছিলেন; সে শুধু শর্ত মেলায়।li p— সম্পর্ক দিয়ে সরু করা। দুটি নামের মাঝের ফাঁকা জায়গাটি একটি combinator — মানে “li-এর ভেতরে থাকা p”. এবার ডেলিভারির লাইন (১৪) বাদ পড়ল, কারণ সেটি কোনো li-এর ভেতরে নেই।.sale .price— class আর সম্পর্ক একসাথে। sale কার্ডের ভেতরের দাম: লাইন ৬ আর ১২. মধুর দামও.price, কিন্তু তার কার্ডে sale নেই।[data-stock="0"] h3— attribute দিয়ে। কোনো class না দিয়েও শুধু attribute-এর মান দেখে স্টক-শূন্য কার্ডটি চেনা গেল, আর তার ভেতরের শিরোনামটি ধরা পড়ল। পুরো পর্বটি আসলে এই সাতটি ধাপেরই বিস্তার।
একটি selector-এর ফল সবসময় element-এর একটি দল — শূন্য, এক বা হাজারটি। CSS-এ “এই selector কাজ করছে না” কথাটির মানে প্রায় সবসময় একটাই: দলটি খালি, অর্থাৎ DOM-এ এমন কোনো element নেই যা শর্ত মানে। তখন CSS কোনো error দেখায় না, চুপচাপ কিছুই করে না। তাই প্রথম প্রশ্ন হবে, “আমার selector আসলে কাকে ধরছে?”
২ · তিন মৌলিক selector — type, class আর id
Type selector — ট্যাগের নাম দিয়ে
Type selector (একে element selector-ও বলে) শুধু ট্যাগের নাম: p, h2, li, button. এটি পাতার সেই ধরনের সব element ধরে। পুরো সাইটের ভিত্তি-স্টাইলের জন্য এটি দারুণ — যেমন সব a-র রং, সব h2-র ফন্ট। কিন্তু “শুধু অফারের দাম” বা “শুধু প্রথম কার্ড” বোঝাতে এটি যথেষ্ট নয়। HTML-এ ট্যাগের নাম case-insensitive, তাই LI লিখলেও কাজ করে — তবে সবাই ছোট হাতের অক্ষরেই লেখেন।
Class selector — একই দলের সবাই
HTML-এ যেকোনো element-এ class attribute দিয়ে এক বা একাধিক নাম লাগানো যায়। Class selector সেই নামের আগে একটি বিন্দু: .sale. একই class যত element-এ খুশি দিতে পারেন — দোকানের তাকে “ছাড়” লেখা লাল স্টিকার যেমন যত পণ্যে খুশি লাগানো যায়।
- এক element-এ কয়েকটি class:
class="card sale"— নামগুলো ফাঁকা জায়গা দিয়ে আলাদা। এর মানে দুটি class, “card sale” নামের একটি নয়। তাই class-এর নামের ভেতরে ফাঁকা জায়গা রাখা যায় না। - দুটি class একসাথে আছে এমন element:
.card.sale— মাঝে কোনো ফাঁকা নেই। একে বলে compound selector: একই element-কে সব শর্ত মানতে হবে। আর.card .sale(মাঝে ফাঁকা) সম্পূর্ণ আলাদা জিনিস — “card-এর ভেতরে থাকা sale”. একটি ফাঁকা জায়গা পুরো অর্থ বদলে দেয়; নতুনদের সবচেয়ে বেশি ভুল এখানেই। - ট্যাগ আর class একসাথে:
p.price— শুধু সেই p, যার class-এ price আছে।
Class-এর নাম রাখার অভ্যাস। নাম রাখুন কাজ বা অর্থ দেখে, চেহারা দেখে নয়। .price, .product-card, .error-message ভালো; .green-text, .big-red খারাপ — কাল ডিজাইনার দামকে নীল করতে বললে .green-text-এর ভেতরে নীল রং লেখা হাস্যকর। ছোট হাতের অক্ষর আর হাইফেন (kebab-case) সবচেয়ে প্রচলিত। মনে রাখবেন, class-এর নাম case-sensitive: .Sale আর .sale আলাদা। বড় প্রজেক্টে নাম গোছানোর পদ্ধতি — যেমন BEM বা utility class — পর্ব ৪৬-এ বিস্তারিত।
Id selector — পাতায় একজনই
Id selector হলো # আর তারপর id-এর নাম: #featured. একটি id পাতায় একবারই থাকে, তাই এটি সবসময় একটি element-কে বোঝায় (ভুল করে একই id দুবার দিলে CSS অবশ্য দুটিকেই ধরে, কিন্তু HTML তখন অবৈধ)।
Id-এর আসল কাজগুলো স্টাইলের বাইরে: পাতার ভেতরে লাফ দেওয়ার লিংক (href="#offer", পর্ব ৫), ফর্মে <label for>-এর সাথে input জোড়া (পর্ব ৮), aria-labelledby (পর্ব ১২), আর JavaScript-এ একটি নির্দিষ্ট element খুঁজে নেওয়া। স্টাইলের জন্য id প্রায় কখনো ব্যবহার করবেন না — class দিন। কারণ দুটি:
- পুনর্ব্যবহার নেই:
#featured-এর স্টাইল আরেকটি কার্ডে চাইলে আরেকটি id লাগবে, আর একই CSS আবার লিখতে হবে। Class হলে শুধু নামটি লাগিয়ে দিলেই হয়। - অতিরিক্ত শক্তি: দুটি নিয়ম একই element-এ ভিন্ন কথা বললে কোনটি জিতবে, তা ঠিক হয় specificity দিয়ে। সেখানে একটি id-কে কোনো সংখ্যক class মিলেও হারাতে পারে না। ফলে পরে বদলাতে গেলে আরও শক্ত selector বা
!importantলাগে — একটি অস্বস্তিকর প্রতিযোগিতা শুরু হয়। পুরো হিসাব পরের পর্বে (পর্ব ১৮)।
এবার নিজে বদলে দেখুন। নিচের এডিটরে HTML স্থির (সেই অফার-তালিকা), আপনি শুধু CSS বদলাবেন:
/* type selector — সব li */
li {
list-style: none;
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 4px 12px;
margin: 8px 0;
}
/* class selector — class-এ sale আছে এমন সব */
.sale { background: #fef3c7; }
.price { color: #15803d; font-weight: bold; }
/* id selector — পাতায় একটিই */
#featured { border: 2px solid #db2777; }তিনটি কার্ডই বর্ডার-ঘেরা বাক্স (li), ইলিশ আর আচার হলুদ (.sale), সব দাম সবুজ-মোটা (.price), আর শুধু প্রথম কার্ডে গোলাপি মোটা বর্ডার (#featured). চেষ্টা করুন: (১) .sale-কে .card করুন — তিনটিই হলুদ হবে। (২) #featured-কে #Featured লিখুন — গোলাপি বর্ডার উধাও, কারণ id-এর নাম case-sensitive. (৩) .sale-কে একবার .card.sale আর একবার .card .sale লিখে তুলনা করুন — প্রথমটিতে আগের মতোই দুটি হলুদ, দ্বিতীয়টিতে একটিও না।
৩ · Universal * আর comma দিয়ে grouping
Universal selector * মানে “যেকোনো element”. একা লিখলে পাতার প্রতিটি element ধরে — html, body থেকে শুরু করে সবচেয়ে ছোট span পর্যন্ত। বাস্তবে এর দুটি প্রচলিত ব্যবহার আছে: পুরো পাতায় একটি নিয়ম বসানো (যেমন * { box-sizing: border-box; }, পর্ব ১৯-এ দেখবেন কেন), আর লেআউটের সমস্যা খুঁজতে সব element-এর চারপাশে সাময়িক দাগ টানা। .card * লিখলে মানে হয় “card-এর ভেতরের সব element”.
কয়েকটি ভিন্ন selector-এ একই স্টাইল দিতে চাইলে প্রতিটির জন্য আলাদা নিয়ম লিখতে হয় না। কমা দিয়ে আলাদা করে লিখলেই হলো: h2, h3 { … }. একে বলে selector list বা grouping. বিয়ের দাওয়াতের কার্ডে “বর ও কনে পক্ষের সবাই” লেখার মতো — দুটি দল, একটি ঘোষণা।
একটি পুরোনো ফাঁদ: কমা দেওয়া তালিকার যেকোনো একটি selector অবৈধ হলে — বানান ভুল, বা ব্রাউজার চেনে না এমন নতুন কিছু — ব্রাউজার পুরো নিয়মটিই বাতিল করে, বাকি ঠিক selector-গুলোসহ। এটি spec-এর নিয়ম, ব্রাউজারের বাগ নয়। তাই নতুন, সব ব্রাউজারে না থাকা selector কখনো পুরোনো selector-এর সাথে একই তালিকায় রাখবেন না। (এর আধুনিক সমাধান :is(), অংশ ৮-এ।)
/* universal: প্রতিটি element-এর চারপাশে দাগ */
* { outline: 1px dashed #f43f5e; }
/* grouping: একই নিয়ম কয়েকটি selector-এ */
h2, h3 {
font-family: system-ui, sans-serif;
color: #1d4ed8;
}
.price, .note { font-size: 14px; }
/* একটি selector ভুল হলে পুরো নিয়ম বাতিল */
h3, p:hovr { text-decoration: underline; }প্রতিটি element-এর চারপাশে লাল ভাঙা দাগ — সবচেয়ে বাইরে html আর body-রও। দুই শিরোনাম নীল, দাম আর নোট ছোট। কিন্তু h3-এর নিচে দাগ (underline) নেই — কারণ শেষ নিয়মে p:hovr বানান ভুল, আর সেই এক ভুলে পুরো নিয়মটি বাতিল। চেষ্টা করুন: :hovr-কে :hover করুন — দুটি h3-এর নিচে দাগ ফিরে আসবে (আর মাউস রাখলে p-এর নিচেও)। তারপর প্রথম লাইনে *-কে li * করুন — এবার শুধু কার্ডের ভেতরের element-এ দাগ থাকবে।
৪ · Attribute selector — attribute দেখে বাছাই
সব element-এ class থাকে না, আর থাকা উচিতও নয়। একটি লিংক PDF-এ যাচ্ছে কি না, একটি input ইমেইলের কি না — এই তথ্য তো HTML-এ আগেই লেখা আছে, attribute-এর ভেতরে। Attribute selector সরাসরি সেটা পড়ে। লেখা হয় বর্গ বন্ধনীতে: [attribute] বা [attribute="মান"]. পোস্ট অফিসের বাছাইকারী যেমন খামের ঠিকানার শেষের পোস্টকোড দেখে চিঠি আলাদা থলিতে রাখেন, এটিও তেমনি attribute-এর মানের শুরু, শেষ বা মাঝখান দেখে বাছাই করতে পারে।
| লেখা | মানে | উদাহরণ যাকে ধরে |
|---|---|---|
[href] | attribute-টি আছে (মান যা-ই হোক) | যেকোনো লিংক |
[type="email"] | মান হুবহু এটি | <input type="email"> |
[href^="https"] | মান এটি দিয়ে শুরু | নিরাপদ বাইরের লিংক |
[href$=".pdf"] | মান এটি দিয়ে শেষ | PDF ফাইলের লিংক |
[href*="dokan"] | মানের ভেতরে কোথাও আছে | ঠিকানায় dokan শব্দ |
[class~="sale"] | ফাঁকা জায়গায় ভাগ করা শব্দের একটি শব্দ হুবহু এটি | class="card sale new" |
[lang|="en"] | মান হুবহু এটি, অথবা এটি আর তারপর হাইফেন | lang="en", lang="en-GB" |
[href$=".pdf" i] | শেষে i: বড়-ছোট হাতের অক্ষরের পার্থক্য উপেক্ষা | .pdf, .PDF, .Pdf |
চিহ্নগুলো মনে রাখার একটি সহজ উপায় আছে: ^ লেখার শুরুতে বসা টুপি — শুরু; $ অনেক প্রোগ্রামিং ভাষার regular expression-এ লাইনের শেষ বোঝায়; * “যেকোনো জায়গায়”। নিচের স্টেপারে একই সাতটি element-এর ওপর আটটি ধরন একে একে চালিয়ে দেখুন:
1<a href="https://www.bkash.com/">2<a href="/files/Price-List.PDF">3<a href="mailto:help@dokan.com.bd">4<a href="#top">5<input type="email" name="email">6<p lang="en-GB">7<p class="card sale new">
| selector | |
| ধরা পড়ল |
[href]— শুধু উপস্থিতি। মান যা-ই হোক, href attribute থাকলেই ধরা পড়ে: চারটি লিংক। input আর p-তে href নেই।^=— শুরু মেলে। শুধু bKash-এর লিংকটি “https” দিয়ে শুরু। এভাবে বাইরের সাইটের লিংকে আলাদা চিহ্ন দেওয়া যায়।$=— শেষ মেলে… কিন্তু ধরা পড়ল না! ফাইলের নাম.PDF— বড় হাতের অক্ষরে। Attribute-এর মান তুলনা সাধারণত হুবহু, বড়-ছোট হাতের অক্ষরসহ। তাই.pdfআর.PDFআলাদা।iflag — বড়-ছোট এক। বন্ধনী বন্ধের আগে একটি ফাঁকা দিয়েiলিখলে তুলনাটি case-insensitive হয়। এবার PDF লিংকটি মিলল। এটি বহু বছর ধরে সব প্রধান ব্রাউজারে আছে (Baseline Widely available).*=— ভেতরে কোথাও। ইমেইল ঠিকানার মাঝখানে “dokan” আছে, তাই মিলল। সাবধান:*=খুব উদার —[class*="sale"]লিখলেwholesaleনামের class-ও ধরা পড়ত।~=— পুরো একটি শব্দ। মানটিকে ফাঁকা জায়গায় ভেঙে শব্দের তালিকা বানানো হয়; তার যেকোনো একটি হুবহু “sale” হলে মেলে।.saleআসলে ঠিক এই কাজটিই করে — class selector হলো[class~="sale"]-এর ছোট রূপ।|=— মান, অথবা মান আর হাইফেন। ভাষার কোডের জন্য বানানো:en,en-GB,en-USসব ধরা পড়ে, কিন্তুengনয়। বাংলার জন্য[lang|="bn"]ধরবেbnআরbn-BDদুটোকেই (ভাষা চেনার জন্য আধুনিক:lang(bn)pseudo-class-ও আছে)।=— হুবহু মান।[type="email"]শুধু ইমেইলের ঘরটি ধরে। ফর্মে আলাদা আলাদা input-কে class ছাড়াই আলাদা স্টাইল দেওয়ার এটিই সবচেয়ে প্রচলিত উপায়।
a[href^="https"] { color: #15803d; }
a[href$=".pdf" i] { color: #b91c1c; font-weight: bold; }
a[href^="mailto:"] { color: #7c3aed; }
a[href^="#"] { color: #64748b; }
input[type="email"] { border: 2px solid #2563eb; }
label { display: block; margin: 6px 0; }
[lang|="en"] { font-style: italic; }চারটি লিংক চার রঙে: bKash সবুজ, PDF লাল-মোটা, ইমেইল বেগুনি, “ওপরে যান” ধূসর — একটিতেও class নেই। ইমেইলের ঘরে নীল মোটা বর্ডার, মোবাইলের ঘর সাধারণ। ইংরেজি লাইনটি বাঁকা (italic). চেষ্টা করুন: PDF-এর নিয়ম থেকে i মুছে দিন — লাল রং চলে যাবে। তারপর input[type="email"]-কে input:not([type="email"]) লিখুন — এবার উল্টো, মোবাইলের ঘরটিতে বর্ডার আসবে। (লিংকে ক্লিক করলে প্রিভিউ অন্য পাতা খোলে না, শুধু জানায়।)
কোথায় কাজে লাগে: ফর্মের input-কে type দেখে স্টাইল, [aria-current="page"] দিয়ে মেনুর বর্তমান পাতা চিহ্নিত করা (স্টাইল আর অ্যাক্সেসিবিলিটি একসাথে ঠিক থাকে), [disabled] বা [aria-expanded="true"] দিয়ে অবস্থা দেখানো, আর [data-stock="0"]-এর মতো নিজের বানানো data attribute দিয়ে JavaScript-এর সাথে যোগাযোগ। যখন কোনো তথ্য ইতিমধ্যে attribute-এ আছে, তখন তার জন্য আলাদা class বানানো মানে একই কথা দুবার লেখা — আর দুটি একসময় আলাদা হয়ে যেতে পারে।
৫ · Combinator — সম্পর্ক দিয়ে বাছাই
পরিবারে কাউকে চেনাতে আমরা প্রায়ই সম্পর্ক ব্যবহার করি: “রহিম সাহেবের নাতি”, “করিমের ছেলে”, “বড় বোনের ঠিক পরের জন”। DOM গাছেও প্রতিটি element-এর parent, child, sibling আর ancestor আছে (পর্ব ৩)। Combinator হলো দুটি selector-এর মাঝখানের সেই চিহ্ন, যা এই সম্পর্ক বোঝায়। চারটি combinator:
␣ Descendant (ফাঁকা জায়গা)
article p — article-এর ভেতরে যেকোনো গভীরতায় থাকা p. নাতি-নাতনি, তাদের সন্তান — সবাই। Descendant combinator সবচেয়ে বেশি ব্যবহৃত।
> Child
article > p — শুধু article-এর সরাসরি সন্তান p. মাঝখানে অন্য element থাকলে চলবে না। এটি child combinator.
+ Next-sibling
h2 + p — h2-এর ঠিক পরের ভাই-বোন, যদি সে p হয়। একই parent, মাঝে আর কেউ নেই। এটি next-sibling combinator.
~ Subsequent-sibling
h2 ~ p — h2-এর পরে আসা সব ভাই-বোন p, মাঝে অন্য কিছু থাকলেও। এটি subsequent-sibling combinator.
দুটি নিয়ম খেয়াল রাখুন। প্রথমত, সব combinator-এ ফল হয় ডান দিকের element — h2 + p p-কে ধরে, h2-কে নয়। দ্বিতীয়ত, sibling combinator শুধু সামনের দিকে দেখে: h2 ~ p h2-এর আগের p-কে ধরে না। (আগের ভাই-বোন বা parent-কে ধরার ক্ষমতা অনেক পরে এসেছে :has()-এর মাধ্যমে — পর্ব ৩৪-এ।) একটি খিচুড়ির রেসিপির গাছে চারটিকে পাশাপাশি দেখুন:
1<article>2 <h2>খিচুড়ির রেসিপি3 <p>ভূমিকা4 <p>উপকরণ5 <div class="tip">6 <p>টিপস: চাল আগে ভিজিয়ে রাখুন7 </div>8 <p>পরিবেশন9</article>
| selector | |
| ধরা পড়ল |
- Descendant —
article p. চারটি p-ই article-এর বংশধর। টিপসের p আছেdiv-এর ভেতরে, অর্থাৎ article-এর “নাতি” — তবুও ধরা পড়ল, কারণ ফাঁকা জায়গা মানে যেকোনো গভীরতা। - Child —
article > p. এবার টিপস (লাইন ৬) বাদ। তার parent হলোdiv.tip, article নয়। বাকি তিনটি p সরাসরি article-এর সন্তান। - Next-sibling —
h2 + p. h2-এর ঠিক পরের ভাই-বোন হলো “ভূমিকা”, আর সে p — তাই শুধু সে। ব্লগে শিরোনামের পরের প্রথম অনুচ্ছেদকে একটু বড় করে দেখাতে এটি খুব প্রচলিত। - Subsequent-sibling —
h2 ~ p. h2-এর পরে একই parent-এর নিচে যত p আছে: ভূমিকা, উপকরণ, পরিবেশন। মাঝেdivথাকলেও পরিবেশন ধরা পড়ল। কিন্তু টিপস নয় — সে h2-এর ভাই-বোন নয়, ভাই-বোনের সন্তান। .tip > p. class আর combinator মিলিয়ে: tip বাক্সের সরাসরি সন্তান p — শুধু টিপস। Combinator-এর দুই পাশে যেকোনো ধরনের selector বসতে পারে।.tip + p. tip বাক্সের ঠিক পরের ভাই-বোন p — “পরিবেশন”। খেয়াল করুন, এখানে আগের element টি একটি div, p নয়;+শুধু দেখে ঠিক পরের জন কে।
/* descendant: article-এর ভেতরে যেকোনো গভীরতায় p */
article p { color: #64748b; }
/* child: article-এর সরাসরি সন্তান p */
article > p { border-left: 4px solid #f59e0b; padding-left: 8px; }
/* next-sibling: h2-এর ঠিক পরের p */
h2 + p { font-weight: bold; }
/* subsequent-sibling: h2-এর পরের সব ভাই-বোন p */
h2 ~ p { background: #fef9c3; }
.tip { border: 1px dashed #94a3b8; padding: 0 8px; }প্রতিটি combinator আলাদা একটি property বদলাচ্ছে, তাই একসাথে মিলিয়ে দেখা যায়। চারটি p-ই ধূসর লেখা (descendant). ভূমিকা, উপকরণ আর পরিবেশনের বাঁয়ে কমলা দাগ আর হলুদ পটভূমি (child আর subsequent-sibling), কিন্তু ভাঙা দাগের বাক্সে থাকা টিপসে দুটির কোনোটিই নেই। শুধু ভূমিকা মোটা (next-sibling). চেষ্টা করুন: h2 + p-কে .tip + p করুন — এবার মোটা হবে “পরিবেশন”। তারপর article > p-কে article > div > p লিখুন — কমলা দাগ শুধু টিপসে।
একটি সাবধানতা: লম্বা combinator-এর শিকল — main > div > ul > li > a — HTML-এর গঠনের সাথে শক্ত করে বাঁধা। কাল কেউ ul-এর বাইরে একটি div জুড়ে দিলেই selector ভেঙে যাবে, আর কেউ টেরও পাবে না। সাধারণ নিয়ম: যত কম ধাপে কাজ হয়, তত ভালো — প্রায়ই একটি অর্থবহ class (.nav-link) এই পুরো শিকলের চেয়ে নিরাপদ।
৬ · ব্রাউজার selector পড়ে ডান থেকে বাঁয়ে
আমরা .menu li a বাঁ থেকে ডানে পড়ি: “menu-র ভেতরে, li-এর ভেতরে, a”. স্বাভাবিকভাবে মনে হয়, ব্রাউজারও আগে .menu খুঁজবে, তারপর তার ভেতরে li, তারপর a. কিন্তু আসলে ব্রাউজার উল্টো দিক থেকে কাজ করে।
কারণটা প্রশ্নের ধরনে। ব্রাউজারের আসল প্রশ্ন “এই selector কাকে কাকে ধরে?” নয় — বরং প্রতিটি element-এর জন্য, “এই element-টিতে কোন কোন নিয়ম খাটে?” তাই সে একটি element হাতে নিয়ে selector-এর সবচেয়ে ডানের অংশ — একে বলে key selector — আগে মেলায়। না মিললে সঙ্গে সঙ্গে বাদ, বাকি অংশ পড়ারই দরকার নেই। মিললে বাঁ দিকে এগিয়ে ancestor-দের মধ্যে বাকি শর্ত খোঁজে। বেশিরভাগ element প্রথম ধাপেই বাদ পড়ে, তাই কাজটি দ্রুত হয়।
1<body>2 <nav class="menu">3 <ul>4 <li>5 <a>হোম6 <footer>7 <a>যোগাযোগ
| যাচাই হচ্ছে | |
| বাকি অংশ | |
| অবস্থা |
- শুরু ডান দিক থেকে। Selector-এর সবচেয়ে ডানের অংশ
a. গাছে দুটি a — হোম আর যোগাযোগ — শুধু এরাই প্রার্থী। body, nav, footer-এর মতো বাকি element-এর জন্য এই নিয়ম নিয়ে আর ভাবতেই হলো না। - প্রথম প্রার্থী: “হোম”। সে a, তাই key selector মিলল। এবার বাঁ দিকের পরের অংশ —
li— তার ancestor-দের মধ্যে আছে কি না দেখতে হবে। - ওপরে উঠে li খোঁজা। হোমের parent-ই একটি li — মিলল। ফাঁকা জায়গা (descendant) বলে parent না হয়ে আরও ওপরের কেউ হলেও চলত।
- আরও ওপরে
.menu. li-এর parent ul — class-এ menu নেই, তাই আরও ওপরে; nav-এর class-এ menu আছে — মিলল! সব অংশ মিলেছে, তাই এই নিয়ম “হোম”-এর জন্য খাটবে। এরপর কোন নিয়ম জিতবে, সেটি ঠিক করবে cascade (পর্ব ১৮)। - দ্বিতীয় প্রার্থী: “যোগাযোগ”। key selector a মিলল, কিন্তু ওপরে li খুঁজতে গিয়ে পাওয়া গেল footer, তারপর body, তারপর গাছ শেষ। কোনো li নেই — তাই
.menuখোঁজার প্রশ্নই উঠল না। বাদ। - ফল। একটি মিলল, একটি বাদ। খেয়াল করুন, ব্রাউজার কখনো “সব .menu খুঁজে তার ভেতরের সব li-তে নামা”-র কাজ করেনি — প্রতিটি element থেকে শুধু ওপরের দিকে একটি পথ ধরে হেঁটেছে। গাছে ওপরের দিকে পথ সবসময় একটিই, নিচের দিকে শাখা অসংখ্য।
আরও গভীরে: আধুনিক ইঞ্জিন এর ওপর আরও কৌশল চাপায়। CSS পড়ার সময়ই প্রতিটি নিয়মকে তার key selector-এর id, class বা ট্যাগের নাম অনুযায়ী আলাদা “ঝুড়িতে” রাখা হয় — ফলে একটি li-এর জন্য শুধু li-ঝুড়ি, তার class-ঝুড়ি আর *-ঝুড়ির নিয়মগুলোই দেখা হয়, হাজার হাজার নিয়মের সবগুলো নয়। ancestor খোঁজার জন্য Chrome আর Safari-র মতো ইঞ্জিন একটি Bloom filter-এ পূর্বপুরুষদের id, class আর ট্যাগের ছাপ রাখে, যাতে “ওপরে .menu নেই”-এর মতো উত্তর গাছে না হেঁটেই প্রায় এক মুহূর্তে পাওয়া যায়। Firefox-এর Stylo ইঞ্জিন (Rust-এ লেখা) আবার এই মেলানোর কাজ CPU-র কয়েকটি core-এ একসাথে চালায়।
ব্রাউজারের এই selector ইঞ্জিন শুধু CSS-এর জন্য নয়। JavaScript-এর querySelectorAll() আর matches() একই ইঞ্জিনকে প্রশ্ন করে — তাই একটি selector কাকে ধরছে, তা সরাসরি গুনে দেখা যায়। নিচের কোডটি চালান (JavaScript পরে শিখবেন; এখানে শুধু ফলাফল পড়ুন):
// প্রতিটি selector কয়টি element ধরে?
const tests = ['li', '.sale', '#featured', 'p', 'li p',
'.sale .price', '.card.sale', '.card .sale',
'li > .note', '[data-stock="0"] h3'];
for (const s of tests) {
console.log(s.padEnd(20), '->', document.querySelectorAll(s).length);
}
// একটি element কি একটি selector মানে?
const honey = document.querySelectorAll('li')[1];
console.log('honey matches .card ?', honey.matches('.card'));
console.log('honey matches .sale ?', honey.matches('.sale'));
console.log('closest list class :', honey.closest('ul').className);প্রথম স্টেপারের সংখ্যাগুলোই আসবে: li 3, .sale 2, #featured 1, p 5, li p 4, .sale .price 2. নতুন দুটি লাইন খেয়াল করুন: .card.sale 2, কিন্তু .card .sale 0 — সেই এক ফাঁকা জায়গার পার্থক্য। শেষে মধুর কার্ড .card মানে (true), .sale মানে না (false), আর closest() ওপরের দিকে হেঁটে products class-এর ul খুঁজে পেয়েছে — ঠিক ডান-থেকে-বাঁয়ের মতো ওপরে ওঠা। চেষ্টা করুন: তালিকায় 'main > p' যোগ করুন — কত আসে? DOM আর JavaScript নিয়ে আরও জানতে JavaScript কোর্সের DOM পর্বটি দেখুন।
৭ · Pseudo-class-এর এক ঝলক — :hover, :first-child, :nth-child
এ পর্যন্ত সব selector element-কে চিনেছে তার নাম, class বা attribute দিয়ে — যা HTML-এ লেখা আছে। কিন্তু কিছু তথ্য HTML-এ লেখা থাকে না: মাউস এখন কোন element-এর ওপর? এই li তালিকার প্রথম না তৃতীয়? Pseudo-class এই অবস্থা আর অবস্থান দেখে বাছাই করে। শুরুতে একটি কোলন থাকে, আর এটি অন্য selector-এর গায়ে লেগে বসে: li:first-child, a:hover.
:hover— মাউস যখন element-এর ওপর। ফোনে মাউস নেই, তাই সেখানে আঙুল ছোঁয়ালে আচরণ ব্রাউজারভেদে ভিন্ন হয় — জরুরি কোনো তথ্য শুধু hover-এ লুকিয়ে রাখবেন না।:first-child/:last-child— parent-এর প্রথম বা শেষ সন্তান এবং বাকি শর্তও মানে।li:first-childমানে “যে li তার parent-এর প্রথম সন্তান” — “প্রথম li” নয়; প্রথম সন্তান অন্য কিছু হলে কেউ ধরা পড়ে না।:nth-child(…)— ক্রম দিয়ে বাছাই::nth-child(3)তৃতীয়,odd/evenবিজোড়/জোড়, আর:nth-child(3n)প্রতি তিন নম্বরে একটি। গোনা শুরু ১ থেকে, ০ থেকে নয়।
li { padding: 6px 10px; }
/* parent-এর প্রথম সন্তান */
li:first-child { font-weight: bold; }
/* জোড় নম্বরের সারি — ২, ৪, ৬ */
li:nth-child(even) { background: #f1f5f9; }
/* ঠিক তৃতীয়টি */
li:nth-child(3) { color: #b91c1c; }
/* মাউস ওপরে থাকলে */
li:hover { background: #dcfce7; }চাল মোটা অক্ষরে, দ্বিতীয়, চতুর্থ আর ষষ্ঠ সারি হালকা ধূসর (টেবিলের “zebra stripe”-এর মতো), সয়াবিন তেল লাল। মাউস যেকোনো সারির ওপর রাখলে সেটি সবুজ হয়। চেষ্টা করুন: even-কে odd করুন, তারপর 3n — কোন কোন সারি রং পেল? আর li:first-child-কে h3 + ul li:last-child লিখে দেখুন — শেষের লবণ মোটা হবে।
Pseudo-class-এর জগৎ অনেক বড়: :focus-visible (কি-বোর্ড ব্যবহারকারীর জন্য জরুরি), :checked, :user-invalid (পর্ব ৯-এ দেখেছেন), :empty, :target আর parent বাছাইয়ের :has(). পুরো আলোচনা — সাথে ::before-এর মতো pseudo-element — পর্ব ৩৪-এ।
৮ · :is(), :where() আর :not() — আধুনিক grouping
ধরুন header, main আর footer — তিন জায়গার লিংককেই একই স্টাইল দিতে চান, কিন্তু অন্য কোথাও নয়। পুরোনো উপায়ে লিখতে হতো header a, main a, footer a. বড় সাইটে এই তালিকা বিশাল হয়ে যায়। তিনটি আধুনিক pseudo-class এই কাজ সহজ করে, এবং তিনটিই এখন সব প্রধান ব্রাউজারে আছে (Baseline Widely available).
:is(…)— বন্ধনীর ভেতরের তালিকার যেকোনো একটি মানলেই হলো।:is(header, main, footer) a= ওপরের লম্বা তালিকা।.card :is(h2, h3)= card-এর ভেতরের h2 বা h3.:where(…)— কাজে হুবহু:is()-এর মতো, পার্থক্য শুধু শক্তিতে (নিচে দেখুন)।:not(…)— উল্টো: তালিকার কোনোটিই না মানলে।li:not(.done)= যে কাজগুলো এখনো বাকি। আধুনিক ব্রাউজারে এর ভেতরে কমা দেওয়া তালিকা আর combinator-ও চলে:a:not(nav a)= nav-এর বাইরের লিংক।
Specificity-তে তিনজনের পার্থক্য (পুরো হিসাব পর্ব ১৮-এ): :is() আর :not()-এর শক্তি হয় তাদের তালিকার সবচেয়ে শক্তিশালী সদস্যের সমান — তালিকায় একটি id থাকলে পুরো selector id-এর মতো শক্ত। আর :where()-এর শক্তি সবসময় শূন্য, ভেতরে যা-ই থাকুক। এজন্য লাইব্রেরি আর রিসেট স্টাইলশিটে :where() জনপ্রিয় — এমন ডিফল্ট স্টাইল দেওয়া যায়, যা পরে যে কেউ একটি সাধারণ selector দিয়ে বদলাতে পারে।
/* nav অথবা footer-এর ভেতরের লিংক */
:is(nav, footer) a { color: #7c3aed; font-weight: bold; }
/* done class নেই এমন li */
li:not(.done) { color: #b91c1c; }
li.done { text-decoration: line-through; }
/* specificity: :where() = 0, তাই নিচের h3 জেতে */
:where(.card) h3 { color: crimson; }
h3 { color: seagreen; }nav আর footer-এর তিনটি লিংক বেগুনি-মোটা, কিন্তু কার্ডের ভেতরের “শর্ত দেখুন” সাধারণ নীল। চাল কেনা কাটা দাগে, বাকি দুটি লাল। শিরোনাম “ঈদের অফার” সবুজ — কারণ :where(.card) h3-এর শক্তি শুধু h3-এর সমান, আর সমান হলে পরে লেখা নিয়ম জেতে। চেষ্টা করুন: :where-কে :is করুন — শিরোনাম লাল হয়ে যাবে, কারণ এবার .card-এর class-শক্তি যোগ হলো। তারপর প্রথম নিয়মে :is(nav, footer)-এর তালিকায় .card যোগ করুন।
ক্ষমাশীল তালিকা: অংশ ৩-এ দেখেছেন, সাধারণ কমা-তালিকায় একটি ভুল selector পুরো নিয়ম বাতিল করে। :is() আর :where()-এর ভেতরের তালিকা কিন্তু forgiving selector list — অচেনা বা ভুল selector শুধু বাদ পড়ে, বাকিগুলো কাজ করে। তাই :is(h3, p:hovr) লিখলে h3 ঠিকই স্টাইল পাবে। :not() অবশ্য ক্ষমাশীল নয়। এই pseudo-class-গুলোর আগে Firefox আর Safari নিজস্ব নামে (:-moz-any(), :-webkit-any()) একই কাজ পরীক্ষামূলকভাবে করত; সেই অভিজ্ঞতা থেকেই আজকের মানসম্মত :is() এসেছে। পুরোনো সেই নাম আজ আর ব্যবহার করার কারণ নেই।
৯ · Selector-এর গতি — ভুল ধারণা, আসল খরচ আর selector খেলা
ইন্টারনেটে প্রায়ই পরামর্শ দেখবেন: “* ব্যবহার করবেন না, ধীর”, “descendant selector এড়িয়ে চলুন”, “id সবচেয়ে দ্রুত, তাই id দিন”। এগুলোর শিকড় ২০০০ সালের দিকের কিছু লেখায়, যখন কম্পিউটার আর ব্রাউজার ইঞ্জিন দুটোই অনেক ধীর ছিল। আজকের অবস্থা আলাদা।
| প্রচলিত কথা | আজকের বাস্তবতা |
|---|---|
“* পাতাকে ধীর করে” | সাধারণ পাতায় পার্থক্য মাপাই কঠিন। * { box-sizing: border-box; } প্রায় সব আধুনিক সাইটে আছে। |
| “descendant selector ব্যয়বহুল” | ডান-থেকে-বাঁয়ে মেলানো, ঝুড়ি আর Bloom filter-এর কারণে আধুনিক ইঞ্জিনে খরচ খুবই কম। |
| “id দিলে দ্রুত, তাই id দিন” | গতির পার্থক্য নগণ্য, কিন্তু specificity-র ক্ষতি বাস্তব (অংশ ২)। Class দিন। |
| “selector যত ছোট, পাতা তত দ্রুত” | ছোট selector ভালো — তবে মূল কারণ পড়তে সহজ আর HTML বদলালেও না ভাঙা, গতি নয়। |
তাহলে আসল খরচ কোথায়? বড় সাইটে CSS-এর গতির সমস্যা সাধারণত আসে (১) বিশাল DOM থেকে — হাজার হাজার element, প্রতিটির জন্য হাজার হাজার নিয়ম; (২) JavaScript ঘনঘন DOM বা class বদলালে বারবার style recalculation থেকে; আর (৩) ব্যবহার না হওয়া বিশাল CSS ফাইল ডাউনলোড আর পড়ার সময় থেকে (পর্ব ৪৯)। অনুমান না করে মাপুন: Chrome DevTools-এর Performance প্যানেলে রেকর্ড করলে “Recalculate Style” অংশে দেখা যায় স্টাইল হিসাবে কত সময় গেল, আর সাম্প্রতিক সংস্করণে selector-ভিত্তিক পরিসংখ্যান (selector stats) চালু করার অপশনও আছে।
গতির ভয়ে নয় — গতি মাপুন DevTools দিয়ে।
Selector খেলা — কোন কোন নম্বর সবুজ হবে?
এবার অনুশীলনের পালা। নিচের HTML-এ প্রতিটি element-এর গায়ে একটি নম্বর লেখা আছে। এডিটর চালানোর আগে কাগজে লিখুন রাউন্ড ১-এ কোন কোন নম্বর সবুজ হবে — তারপর মিলিয়ে দেখুন। পরের রাউন্ডগুলোর জন্য প্রথম লাইনের selector বদলে মন্তব্যে দেওয়া selector-গুলো একে একে বসান; উত্তর আছে অনুশীলন ১-এ।
/* রাউন্ড ১ — আগে অনুমান, তারপর দেখুন */
.menu > p { background: lightgreen; }
/* পরের রাউন্ড: ওপরের .menu > p মুছে একে একে লিখুন
২) li + li
৩) .hot ~ p
৪) ul ~ p
৫) :not(ul) .hot */
.menu { border: 1px dashed #94a3b8; padding: 0 10px; }রাউন্ড ১-এ সবুজ হবে ১, ৫ আর ৬ — .menu-এর তিনটি সরাসরি সন্তান p. ৭ নম্বরও p, কিন্তু সে ভাঙা দাগের বাক্সের বাইরে, menu-র সন্তান নয়। ২–৪ li, তাই প্রশ্নই ওঠে না। বাকি রাউন্ডে নিজের অনুমান লিখে তারপর selector বসান। রাউন্ড ৫ একটি সত্যিকারের ফাঁদ — অনেক অভিজ্ঞ ডেভেলপারও প্রথমবার ভুল করেন।
১০ · AI-এর যুগে — selector লেখার কাজ AI-কে দিলে কী দেখবেন
“এই কার্ডের দামটা সবুজ করে দাও” — AI মুহূর্তেই CSS দেবে। সমস্যা হলো, AI আপনার আসল DOM দেখে না; সে অনুমান করে HTML দেখতে কেমন। তাই তার selector প্রায়ই হয় অতিরিক্ত লম্বা, অতিরিক্ত শক্ত, অথবা এমন কাঠামো ধরে নেয় যা আপনার পাতায় নেই — আর CSS কোনো error দেখায় না বলে ভুলটা চুপচাপ থেকে যায়।
AI সাধারণত যেসব selector-ভুল করে
| AI-এর ভুল | কেন সমস্যা | ঠিক উপায় |
|---|---|---|
div > div > div:nth-child(2) > span | HTML একটু বদলালেই ভাঙে, পড়ে বোঝা যায় না | অর্থবহ একটি class |
স্টাইলে #id, তার ওপর !important | specificity-র লড়াই, পরে বদলানো কঠিন | class, আর পর্ব ১৮-এর cascade বোঝা |
.card .sale লিখেছে, চেয়েছিলেন .card.sale | কিছুই ধরে না, error-ও দেখায় না | DevTools-এ গুনে দেখা |
[class="btn"] | class="btn primary" হলে মেলে না — মান হুবহু চায় | .btn |
| পুরোনো selector-এর সাথে নতুন, অসমর্থিত selector একই কমা-তালিকায় | পুরোনো ব্রাউজারে পুরো নিয়ম বাতিল | আলাদা নিয়ম, অথবা :is() |
.blue-text, .mt-20px ধরনের চেহারা-ভিত্তিক নাম মিশিয়ে ফেলা | কোনো পদ্ধতি নেই, div soup-এর সাথে মিলে জট | একটি নামকরণ পদ্ধতি বেছে নেওয়া (পর্ব ৪৬) |
AI-কে selector লিখতে দেওয়ার আগে তাকে আসল HTML দিন আর শর্তগুলো বলে দিন। শেষ লাইনটি খেয়াল করুন — AI-কে প্রতিটি selector কাকে ধরে তা বলতে বাধ্য করলে তার ভুল অনুমান নিজেই বেরিয়ে আসে।
AI-এর দেওয়া selector হাতে পেলে — যাচাইয়ের চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা ফ্রন্ট-এন্ড ইন্টারভিউর পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে:
- class আর id selector-এর পার্থক্য? — class (
.x) অনেক element-এ বসে, id (#x) পাতায় একটিই; id-এর specificity অনেক বেশি, তাই স্টাইলে class ভালো। .a.bআর.a .b-এর পার্থক্য? — প্রথমটি একই element-এ দুটি class, দ্বিতীয়টি a-এর ভেতরে থাকা b.div p,div > p,div + p,div ~ p? — যেকোনো গভীরতার বংশধর, সরাসরি সন্তান, ঠিক পরের ভাই-বোন, পরের সব ভাই-বোন।- ব্রাউজার selector কোন দিক থেকে মেলায়, কেন? — ডান থেকে বাঁয়ে; key selector না মিললেই element বাদ, তাই বেশিরভাগ যাচাই এক ধাপে শেষ।
:is()আর:where()-এর পার্থক্য? — কাজ একই;:is()-এর specificity তালিকার সবচেয়ে শক্ত সদস্যের সমান,:where()-এর সবসময় শূন্য।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে selector-মুখস্থকারী থেকে ব্রাউজারের মতো করে ভাবতে শেখাবে।
প্র ০১CSS-এ “parent selector” — সন্তান দেখে parent বাছাই — এত বছর ছিল না কেন? আর :has() শেষ পর্যন্ত কীভাবে সম্ভব হলো?
২০০০-এর দশকের শুরু থেকেই ডেভেলপাররা চেয়ে আসছিলেন: “যে কার্ডের ভেতরে ছবি আছে, সেই কার্ডটিকে আলাদা স্টাইল দিতে চাই।” সব combinator শুধু নিচের দিকে বা সামনের দিকে দেখে; ওপরের দিকে বা পেছনের দিকে দেখার কোনো উপায় ছিল না। প্রস্তাব বারবার উঠেছে, আর ব্রাউজার নির্মাতারা বারবার একই কারণে না বলেছেন — গতি।
কারণটা অংশ ৬-এর ডান-থেকে-বাঁয়ে মেলানোর ভেতরেই লুকানো। সাধারণ selector-এ একটি element-এর স্টাইল ঠিক করতে শুধু তার নিজের আর তার ওপরের পূর্বপুরুষদের দেখলেই চলে — আর ব্রাউজার গাছ ওপর থেকে নিচে সাজায়, তাই পূর্বপুরুষরা আগেই তৈরি। কিন্তু parent selector মানে parent-এর স্টাইল নির্ভর করবে তার নিচের সব বংশধরের ওপর। পাতার গভীরে কোথাও একটি ছোট element যোগ হলে ওপরের দিকের অনেক element-এর স্টাইল নতুন করে হিসাব করতে হতে পারে। পাতা লোড হওয়ার সময় HTML একটু একটু করে আসে — তখন প্রতিটি নতুন টুকরো আসার পর আগের সব কিছু আবার যাচাই করতে হবে কি না, সেই প্রশ্নও ছিল।
সমাধান এসেছে ইঞ্জিনিয়ারিং দিয়ে, কোনো জাদু দিয়ে নয়। ব্রাউজারগুলো এমন invalidation কৌশল বানাল, যাতে কোনো পরিবর্তন হলে শুধু সেই element-গুলোই আবার যাচাই হয়, যাদের ওপর :has()-এর নিয়ম আসলে নির্ভর করে — পুরো পাতা নয়। Safari ২০২২ সালের শুরুতে প্রথম :has() চালু করে, একই বছর Chrome; Firefox যোগ দেয় ২০২৩ সালের ডিসেম্বরে। তখন থেকে এটি Baseline, আর এখন Widely available পর্যায়েও পৌঁছেছে। তবু খুব বড় পাতায় body:has(…)-এর মতো বিশাল পরিসরের নিয়ম সাবধানে ব্যবহার করাই ভালো।
মূল উপলব্ধি: একটি ফিচার “কেন নেই”, তার উত্তর প্রায়ই থাকে ভেতরের অ্যালগরিদমে। selector ডান থেকে বাঁয়ে কেন মেলানো হয় তা বুঝলে বোঝা যায় parent selector কেন কঠিন ছিল — আর কেন সেটি আসতে প্রায় দুই দশক লেগেছে।
প্র ০২অনেক পেশাদার দল CSS-এ id দিয়ে স্টাইল করা নিষেধ করে দেয়। id তো দ্রুত আর স্পষ্ট — তাহলে সমস্যা কোথায়?
সমস্যা গতিতে নয়, রক্ষণাবেক্ষণে। একটি বড় সাইটে CSS লেখেন অনেক মানুষ, অনেক বছর ধরে। সেখানে সবচেয়ে বড় প্রশ্ন হলো, “আমি একটা জিনিস বদলালে আর কোথায় কী ভাঙবে, আর আমার নিয়ম আদৌ জিতবে তো?”
Specificity-র হিসাবে একটি id-এর ওজন এত বেশি যে কোনো সংখ্যক class মিলেও তাকে হারাতে পারে না। ধরুন কেউ লিখলেন #sidebar a { color: gray; }. মাস ছয়েক পরে আরেকজন sidebar-এর একটি বোতাম-লিংক সাদা করতে চাইলেন: .btn { color: white; } — কাজ করল না। তিনি লিখলেন .sidebar .btn — তাও না। শেষে হতাশ হয়ে !important. পরের জনকে তখন আরও শক্ত !important লাগবে। একে মজা করে বলা হয় “specificity war” — যুদ্ধ যত চলে, CSS তত ভঙ্গুর হয়।
২০০০-এর দশকের শেষ দিকে Nicole Sullivan-এর OOCSS (Object-Oriented CSS) ধারণা জনপ্রিয় হয়, যার একটি মূল পরামর্শ ছিল: স্টাইল লাগান class-এ, যাতে একই “বস্তু” — একটি কার্ড, একটি বোতাম — পাতার যেকোনো জায়গায় একই রকম দেখায়। পরে CSS Lint-এর মতো টুলেও স্টাইলে id ব্যবহারে সতর্কবার্তার নিয়ম আসে। Yandex-এ জন্ম নেওয়া BEM পদ্ধতিও সবকিছু এক-class-এর selector-এ রাখার পক্ষে। এই সব পদ্ধতির লক্ষ্য একটাই: সব selector-এর শক্তি কাছাকাছি রাখা, যাতে “কে জিতবে” প্রশ্নের উত্তর হয় সহজ — যে পরে লেখা।
id-এর জায়গা তাই HTML-এর গঠনে: পাতার ভেতরের লিংক, label-input জোড়া, ARIA সম্পর্ক, JavaScript. সত্যিই যদি কোনো কারণে id ধরে স্টাইল দিতে হয় (যেমন অন্যের লেখা HTML, যেখানে class যোগ করতে পারছেন না), তাহলে [id="sidebar"] লিখলে একই element ধরা পড়ে, কিন্তু শক্তি থাকে মাত্র একটি class-এর সমান।
মূল উপলব্ধি: ভালো CSS মানে শুধু আজ ঠিক দেখানো নয়, এক বছর পরে অন্য কেউ নিরাপদে বদলাতে পারা। শক্তি কম রাখা মানে ভবিষ্যতের জন্য দরজা খোলা রাখা।
প্র ০৩CSS-এর selector কীভাবে CSS-এর বাইরে বেরিয়ে JavaScript, টেস্টিং টুল আর ওয়েব স্ক্র্যাপিংয়ের সাধারণ ভাষা হয়ে উঠল?
শুরুতে selector ছিল শুধু স্টাইলের ঠিকানা। JavaScript দিয়ে element খুঁজতে তখন হাতে ছিল প্রায় শুধু getElementById আর getElementsByTagName — “sale class-ওয়ালা কার্ডের ভেতরের দাম” খুঁজতে কয়েক লাইনের লুপ লিখতে হতো, আর প্রতিটি ব্রাউজারে আচরণও আলাদা ছিল।
২০০৬ সালে John Resig-এর jQuery এই ছবিটা বদলে দেয়। তার মূল ধারণা ছিল অসাধারণ সরল: ডেভেলপাররা তো CSS selector আগেই জানেন — তাহলে JavaScript-এও সেটাই ব্যবহার হোক। $('.sale .price') লিখলেই সব দাম হাতে। ভেতরের selector ইঞ্জিন (পরে যার নাম হয় Sizzle) সেই selector ভেঙে ভেঙে, অনেক ক্ষেত্রে ডান থেকে বাঁয়ে মিলিয়ে, element খুঁজত। jQuery এত জনপ্রিয় হয় যে একসময় বিশ্বের বেশিরভাগ বড় ওয়েবসাইটে এটি ছিল।
ব্রাউজার নির্মাতারা বুঝলেন, এই কাজ ব্রাউজারের ভেতরেই থাকা উচিত — তাদের নিজেদের CSS ইঞ্জিন তো এটা আগেই পারে, আর অনেক দ্রুত। এভাবে এলো querySelector() আর querySelectorAll(), যা অংশ ৬-এ ব্যবহার করেছেন। আজ jQuery ছাড়াই একই কাজ হয়, আর ব্রাউজারের CSS আর JavaScript একই selector ইঞ্জিন ভাগ করে নেয়।
এরপর selector আরও দূরে ছড়িয়েছে। Playwright, Cypress বা Selenium-এর মতো টেস্টিং টুলে “এই বোতামে ক্লিক করো” বলতে CSS selector লাগে। Python-এর BeautifulSoup-এর মতো লাইব্রেরি দিয়ে পাতা থেকে তথ্য তুলতেও select('.price'). এমনকি DevTools-এ খোঁজার ঘরেও selector লেখা যায়। ফলে selector-এর দক্ষতা আজ শুধু ডিজাইনারের নয় — টেস্টার, ডেটা অ্যানালিস্ট আর ব্যাক-এন্ড ডেভেলপারেরও কাজে লাগে।
মূল উপলব্ধি: একটি ভালো, ছোট “ভাষা” যখন অনেকে জানে, তখন সেটা নিজের জায়গার বাইরে ছড়িয়ে পড়ে। আর টেস্টিং টুলে কেন অর্থবহ selector (যেমন role বা data-testid) পরামর্শ দেওয়া হয়, তার কারণও এই পর্বের সেই শিক্ষা: div > div > span HTML বদলালেই ভাঙে।
প্র ০৪Class-এর নাম অর্থ দেখে রাখব (.product-card), নাকি কাজ দেখে ছোট ছোট utility (.flex, .p-4)? এ নিয়ে এত বিতর্ক কেন?
এটি CSS জগতের সবচেয়ে দীর্ঘ বিতর্কগুলোর একটি, আর দুই পক্ষেরই শক্ত যুক্তি আছে।
অর্থভিত্তিক (semantic) নামের পক্ষে: HTML বলে “এটা কী” — class="product-card"; CSS আলাদা ফাইলে বলে “দেখতে কেমন”। ২০০৩ সালের CSS Zen Garden দেখিয়েছিল, একই HTML-এ শুধু CSS বদলে পুরো সাইটের চেহারা পাল্টে ফেলা যায়। HTML পরিষ্কার থাকে, পড়তে সহজ। BEM-এর মতো পদ্ধতি (.card__price--sale) এই ধারায় বড় প্রজেক্টে শৃঙ্খলা আনে।
Utility-র পক্ষে: বাস্তবে বড় প্রজেক্টে দেখা গেল, “আলাদা রাখা” CSS ফাইলটিই সবচেয়ে ভয়ের জায়গা হয়ে ওঠে — কেউ কোনো নিয়ম মুছতে সাহস করে না, কারণ কোথায় কোথায় ব্যবহার হচ্ছে কেউ জানে না, তাই ফাইল শুধু বাড়ে। ২০১৭ সালে Adam Wathan তাঁর একটি প্রবন্ধে যুক্তি দেন, অনেক ক্ষেত্রে HTML-এ সরাসরি ছোট ছোট utility class লাগানোই বেশি বাস্তবসম্মত; সেই চিন্তা থেকে আসে Tailwind CSS. Component-ভিত্তিক কাজে (React, Vue) যেখানে HTML নিজেই একটি পুনর্ব্যবহারযোগ্য টুকরো, সেখানে এই পদ্ধতি খুব জনপ্রিয়।
Selector-এর দৃষ্টিকোণ থেকে মজার মিল আছে: দুই পদ্ধতিই প্রায় সবকিছু এক-class-এর selector-এ রাখে — লম্বা combinator-এর শিকল নেই, id নেই। অর্থাৎ এই পর্বে শেখা নীতি — কম শক্তি, কম নির্ভরতা — দুই পক্ষই মানে; তর্কটা মূলত নাম আর ফাইল গোছানো নিয়ে।
নতুন হিসেবে কোনটি শিখবেন? আগে খাঁটি CSS আর অর্থবহ class, কারণ utility framework-ও শেষ পর্যন্ত CSS তৈরি করে, আর সমস্যা হলে DevTools-এ সেই CSS-ই দেখতে হবে। দুটির বিস্তারিত তুলনা পর্ব ৪৬ আর ৫১-এ।
মূল উপলব্ধি: “কোনটা সঠিক” নয় — প্রশ্ন হলো দল, প্রজেক্টের আকার আর কাজের ধরন অনুযায়ী কোনটা টেকসই। তবে যে পদ্ধতিই বাছুন, পুরো প্রজেক্টে একটিই মেনে চলুন।
প্র ০৫“CSS selector ধীর” — এই বিশ্বাস কোথা থেকে এল, আর কোন পরিস্থিতিতে CSS সত্যিই একটি পাতাকে ধীর করে দেয়?
বিশ্বাসটির একটি নির্দিষ্ট উৎস আছে। ২০০০ সালের দিকে Mozilla-র ইঞ্জিনিয়ার David Hyatt (পরে Safari-র WebKit-এর অন্যতম প্রধান ব্যক্তি) “Writing Efficient CSS” নামে একটি লেখা লেখেন। সেখানে তিনি ডান-থেকে-বাঁয়ে মেলানোর ব্যাখ্যা দিয়ে পরামর্শ দেন — descendant selector এড়িয়ে চলুন, key selector হিসেবে * বা সাধারণ ট্যাগ রাখবেন না। কিন্তু লেখাটি ছিল মূলত Firefox-এর নিজের ইন্টারফেস (XUL) বানানোর জন্য, সেই যুগের ধীর কম্পিউটারে। পরামর্শগুলো ওয়েবজুড়ে ছড়িয়ে পড়ল নিয়মের মতো, প্রেক্ষাপট ছাড়াই।
২০০৯ সালে ওয়েব পারফরম্যান্স বিশেষজ্ঞ Steve Souders বাস্তবে পরীক্ষা করে দেখান, সাধারণ পাতায় selector-এর ধরন বদলে যে সময় বাঁচে, তা প্রায় নগণ্য — ছবি, স্ক্রিপ্ট আর নেটওয়ার্কের তুলনায় কিছুই না। এরপর ব্রাউজার ইঞ্জিন আরও অনেক দ্রুত হয়েছে: নিয়মের ঝুড়ি, Bloom filter, ফলাফল ক্যাশ, এমনকি একাধিক CPU core-এ একসাথে মেলানো।
তবু CSS কখন সত্যিই সমস্যা? (১) বিশাল DOM: হাজার হাজার element আর হাজার হাজার নিয়ম — গুণফলটাই বড়। একটি “ইনফিনিট স্ক্রল” ফিড বা বিশাল ডেটা টেবিলে এটা বাস্তব। (২) বারবার style recalculation: JavaScript প্রতি ফ্রেমে body-র class বদলালে পুরো পাতার স্টাইল আবার হিসাব হতে পারে। (৩) খুব বিস্তৃত পরিসরের :has() বা জটিল :nth-child(), যা ছোট পরিবর্তনেও অনেক element আবার যাচাই করায়। (৪) সবচেয়ে সাধারণ — অপ্রয়োজনীয় বড় CSS ফাইল: ব্রাউজার CSS পুরো না পড়া পর্যন্ত পাতা দেখায় না, তাই ব্যবহার না হওয়া হাজার লাইন সরাসরি দেরি ঘটায় (পর্ব ৪৯)।
এগুলো ধরার উপায় অনুমান নয়, মাপা — DevTools-এর Performance প্যানেলে “Recalculate Style” কত সময় নিচ্ছে, আর Coverage প্যানেলে CSS-এর কত অংশ আসলে ব্যবহার হচ্ছে।
মূল উপলব্ধি: পারফরম্যান্সের পরামর্শ সবসময় একটি সময় আর প্রেক্ষাপটের ফসল। প্রশ্ন করুন “এটা কে, কবে, কোন পরিস্থিতিতে মেপেছিলেন?” — তারপর নিজের পাতায় নিজে মাপুন।
অনুশীলন
আগে উত্তর ভাবুন বা কাগজে লিখুন, তারপর মিলিয়ে দেখুন। ওপরের লাইভ এডিটরগুলোতেই যাচাই করা যাবে।
-
Selector খেলার বাকি রাউন্ড: অংশ ৯-এর মেনুর HTML-এ রাউন্ড ২ থেকে ৫-এর (
li + li,.hot ~ p,ul ~ p,:not(ul) .hot) প্রতিটিতে কোন কোন নম্বর সবুজ হবে?রাউন্ড ২ —
li + li: ৩ আর ৪. যে li-এর ঠিক আগে আরেকটি li আছে। চা (২) প্রথম, তার আগে কেউ নেই। তালিকার আইটেমের মাঝে ভাগ-রেখা টানতে এটি খুব কাজের — প্রথমটি বাদে সবার ওপরে দাগ।রাউন্ড ৩ —
.hot ~ p: শুধু ৬. দুটি.hotআছে। কফির (৩) পরের ভাই-বোন শুধু লাচ্ছি, যে li — p নয়। দামের (৫) পরের ভাই-বোন p ঠিকানা (৬)। ৭ নম্বর menu-র বাইরে, ভাই-বোনই নয়।রাউন্ড ৪ —
ul ~ p: ৫ আর ৬. ul-এর পরে একই parent-এর নিচের সব p. ভূমিকা (১) ul-এর আগে, তাই বাদ — sibling combinator পেছনে দেখে না।রাউন্ড ৫ —
:not(ul) .hot: ৩ আর ৫ — ফাঁদ এখানেই। অনেকে পড়েন “ul-এর ভেতরে নয় এমন .hot”, তাই শুধু ৫ ভাবেন। কিন্তু আসল অর্থ: “.hot, যার কোনো একজন পূর্বপুরুষ ul নয়”। কফির পূর্বপুরুষ ul, কিন্তু div.menu, body আর html-ও — তারা তো ul নয়! তাই কফিও ধরা পড়ে। “ul-এর ভেতরে নেই” বোঝাতে লিখতে হয়.hot:not(ul .hot)— এবার শুধু ৫. -
Selector লিখুন: (ক) পাতার সব PDF লিংক, ফাইলের নাম বড় বা ছোট হাতের যা-ই হোক; (খ) প্রতিটি
h2-এর ঠিক পরের অনুচ্ছেদ; (গ) এমন বোতাম, যার class-এbtnআরprimaryদুটোই আছে; (ঘ) footer-এর বাইরের সব লিংক।(ক)
a[href$=".pdf" i]—$=শেষ মেলায়,iবড়-ছোট হাতের অক্ষর এক করে। লিংকের শেষে?v=2-এর মতো query string থাকলে এটি মিলবে না; তখন[href*=".pdf" i]চলে, তবে সেটি একটু বেশি উদার।(খ)
h2 + p— next-sibling combinator.(গ)
.btn.primary— দুটি class-এর মাঝে কোনো ফাঁকা নয়। ট্যাগ নিশ্চিত করতে চাইলেbutton.btn.primary.(ঘ)
a:not(footer a)—:not()-এর ভেতরে complex selector আধুনিক সব ব্রাউজারে চলে (Baseline Widely available).:not(footer) aলিখলে অনুশীলন ১-এর রাউন্ড ৫-এর মতো ফাঁদে পড়বেন — body নিজেও footer নয়, তাই সব লিংকই ধরা পড়বে। -
DevTools-এ selector গোনা: যেকোনো একটি খবরের বা ই-কমার্স সাইট খুলে F12 চাপুন, Elements প্যানেলে Ctrl+F চেপে লিখুন
a, তারপরnav a, তারপরa[href^="https"]. প্রতিবার খোঁজার ঘরের পাশে কী সংখ্যা দেখায়? কোনো একটি class-এর নাম দেখে সেটিও খুঁজুন।Elements প্যানেলের খোঁজার ঘর সাধারণ লেখা, CSS selector আর XPath — তিনটিই বোঝে, আর পাশে দেখায় “১ of ২৩৪”-এর মতো মোট মিলের সংখ্যা। Enter চাপতে থাকলে একে একে প্রতিটি মিলে যাওয়া element-এ নিয়ে যায়। সাধারণত
a-র সংখ্যা কয়েকশো,nav aঅনেক কম।a[href^="https"]থেকে বোঝা যায় কতগুলো লিংক পূর্ণ ঠিকানায় লেখা। বড় সাইটে প্রায়ই দেখবেন অদ্ভুত, স্বয়ংক্রিয়ভাবে তৈরি class-এর নাম (যেমনcss-1x2y3z) — এগুলো build টুলের তৈরি, হাতে লেখা নয়। AI-এর দেওয়া যেকোনো selector যাচাইয়ের সবচেয়ে দ্রুত উপায়ও এই খোঁজার ঘর। -
বাগ খুঁজুন: AI লিখে দিয়েছে
.product-list > .price { color: green; }আর#Offer { border: 2px solid red; }. HTML হলো<ul class="product-list"><li class="card"><p class="price">…</p></li></ul>আর<section id="offer">. কোনোটিই কাজ করছে না, কোনো error-ও নেই। কেন?প্রথমটি:
>মানে সরাসরি সন্তান। কিন্তু.priceআছেli-এর ভেতরে — সে ul-এর নাতি, সন্তান নয়। সমাধান:.product-list .price(descendant), অথবা আরও সহজ — শুধু.price, যদি পাতায় দাম একই রকম দেখানোর কথা হয়।দ্বিতীয়টি: id-এর নাম case-sensitive: HTML-এ
offer, CSS-এOffer. তার ওপর স্টাইলে id ব্যবহারই উচিত নয় —section-এ একটি class দিয়ে (class="offer").offerলিখুন।দুটি ক্ষেত্রেই CSS চুপ থাকে, কারণ ভুল selector-ও বৈধ selector — শুধু তার দলটি খালি। এজন্যই DevTools-এ গুনে দেখা অভ্যাস করুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি <a> element-এর জন্য ব্রাউজার .menu li a নিয়মটি যেভাবে যাচাই করে, সেই ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- সবচেয়ে ডানের অংশ (key selector)
aelement-টির সাথে মেলানো - ওপরের দিকে হেঁটে একটি
liপূর্বপুরুষ খোঁজা - সেই li-এর আরও ওপরে
.menuclass-ওয়ালা পূর্বপুরুষ খোঁজা - সব অংশ মিললে নিয়মটিকে ওই element-এর প্রযোজ্য নিয়মের তালিকায় রাখা
- cascade আর specificity দিয়ে ঠিক করা, প্রতিটি property-তে কোন নিয়ম জিতবে
<button class="btn primary"> element-টিকে কোন selector ধরবে?
.btn .primary.btn.primary[class="btn"]#btn
HTML: <h2>, তারপর <img>, তারপর দুটি <p> — চারটিই একই parent-এর সন্তান। h2 + p কয়টি element ধরবে?
- দুটি p-ই
- শুধু প্রথম p
- একটিও না
- শুধু h2
CSS-এ a[href$=".pdf" i] কোন লিংকগুলো ধরে?
- যেসব লিংকের href-এর শেষে .pdf, বড় বা ছোট যেকোনো হাতের অক্ষরে
- যেসব লিংকের href .pdf দিয়ে শুরু
- যেসব লিংকের href-এর ভেতরে কোথাও pdf আছে
- শুধু ছোট হাতের .pdf, বড় হাতের .PDF বাদ
একটি CSS ফাইলে আছে h1, h2, p:hovr { color: red; } — :hovr বানান ভুল। ব্রাউজার কী করবে?
- ভুলটি নিজে ঠিক করে :hover ধরে নেবে
- শুধু ভুল অংশটি বাদ দিয়ে h1 আর h2-কে লাল করবে
- পুরো CSS ফাইল বাতিল করে error দেখাবে
- পুরো নিয়মটি বাতিল করবে — h1, h2-ও লাল হবে না
:where(#main) p আর p — এই দুটি selector-এর specificity-র সম্পর্ক কী?
- প্রথমটি বেশি, কারণ ভেতরে একটি id আছে
- দুটি সমান
- দ্বিতীয়টি বেশি
- :where() থাকলে specificity হিসাব হয় না
একটি “ছাড়” স্টাইল পাতার অনেক পণ্য-কার্ডে বসাতে হবে, আর পরে সহজে বদলানোর সুযোগও রাখতে হবে। কোনটি সবচেয়ে ভালো?
- প্রতিটি কার্ডে
class="sale"দিয়ে CSS-এ.sale - প্রতিটি কার্ডে
id="sale"দিয়ে CSS-এ#sale main > div > ul > li:nth-child(2)ধরনের লম্বা selector- প্রতিটি কার্ডে আলাদা
style="…"attribute
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Selector DOM গাছ থেকে element-এর একটি দল বাছে; “কাজ করছে না” মানে প্রায় সবসময় দলটি খালি — DevTools-এ গুনে দেখুন।
p,.class,#id,*, আর কমা দিয়ে grouping; স্টাইলে class, id রাখুন লিংক-ফর্ম-JavaScript-এর জন্য।.a.b≠.a .b.- Attribute selector:
[x],=,^=শুরু,$=শেষ,*=ভেতরে,~=শব্দ,|=ভাষা-কোড, আর শেষেiমানে বড়-ছোট এক। - Combinator: ফাঁকা = বংশধর,
>= সন্তান,+= ঠিক পরের,~= পরের সব; ফল সবসময় ডানের element, আর ব্রাউজার মেলায় ডান থেকে বাঁয়ে। :is()/:not()নেয় সবচেয়ে শক্ত সদস্যের specificity,:where()শূন্য; selector বাছুন পড়ার সুবিধা আর টেকসই হওয়া দেখে — গতির ভয়ে নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৮ · Cascade, specificity আর inheritance — কোন নিয়ম জেতেপরবর্তী পর্বএকই element-এ একাধিক নিয়ম লাগলে ব্রাউজার কীভাবে বিজয়ী ঠিক করে।
- পর্ব ৩৪ · Pseudo-class আর pseudo-elementগভীরে যান
:hover,:focus-visible,:has(),::before— অবস্থা আর অবস্থান দেখে বাছাইয়ের পুরো জগৎ। - HTML ও CSS · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — প্রথম পেজ থেকে আধুনিক রেসপন্সিভ ডিজাইন।
- JavaScript · DOM-এর ভিত্তিপাশের ট্র্যাক
querySelector()দিয়ে একই selector-এ element খুঁজে JavaScript দিয়ে বদলানো।