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

আধুনিক CSS — nesting, clamp(), logical property আর @layer

Modern CSS — Native Nesting, Math Functions & Fluid Type, Logical Properties, Cascade Layers & the Small Wins

Taught in Bangla, this lesson tours modern CSS: native nesting and how & behaves like :is() for specificity, min(), max() and clamp() for fluid type, logical properties based on start and end, cascade layers in depth, lightweight selectors with :is() and :where(), small wins like inset, aspect-ratio, accent-color and color-scheme, and a cheat sheet mapping old tricks to new features.

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

  • Native CSS nesting-এর নিয়ম — &, nested media query, আর Sass-এর সাথে পার্থক্য
  • min(), max(), clamp() দিয়ে মাপ বানানো, fluid typography-র সূত্র নিজে বের করা আর তার accessibility-র শর্ত
  • Logical property দিয়ে এমন CSS লেখা, যা বাংলা, উর্দু, আরবি বা খাড়া জাপানি — যেকোনো লেখার দিকে ঠিক থাকে
  • @layer-এর পুরো কৌশল: ক্রম ঘোষণা, বাইরের CSS-কে layer-এ ঢোকানো, !important-এর উল্টো ক্রম
  • কোন পুরোনো hack-এর জায়গায় কোন আধুনিক feature — একটি চিটশিট, আর AI-এর দেওয়া পুরোনো CSS চেনার উপায়

১ · “আধুনিক CSS” মানে কী — ঠেকা দেওয়া থেকে নকশায় সমাধান

ফ্রিল্যান্সার সুমি একটি পুরোনো অনলাইন দোকানের সাইট মেরামতের কাজ পেলেন। CSS ফাইল খুলে দেখলেন — ৪০টির বেশি !important, ভিডিওর বাক্সে padding-top: 56.25% নামের রহস্যময় সংখ্যা, আরবি ভাষার পাতার জন্য পুরো CSS-এর আলাদা একটি উল্টো কপি, আর প্রতিটি পর্দার মাপের জন্য আলাদা আলাদা font-size. সবই কাজ করছে, কিন্তু একটি রং বদলাতে গেলে তিন জায়গায় কিছু না কিছু ভেঙে যায়।

এগুলো ভুল ছিল না — যখন লেখা হয়েছিল, তখন CSS-এ এর চেয়ে ভালো উপায় ছিল না। ডেভেলপাররা যা চাইতেন আর CSS যা দিত, তার মাঝের ফাঁক তাঁরা পূরণ করতেন নানা কৌশল (hack) দিয়ে। গত কয়েক বছরে সেই ফাঁকগুলোর বেশিরভাগ CSS নিজেই ভরাট করেছে। এই পর্বের প্রতিটি feature আসলে একটি পুরোনো hack-এর অবসান।

পুরোনো দালানে ফাটল ধরলে মিস্ত্রি বাঁশের ঠেকা দেন, ওপরে আরেক স্তর প্লাস্টার লাগান। কাজ চলে, কিন্তু দালানের আসল কাঠামো বোঝা কঠিন হয়ে যায়। নতুন দালানে প্রকৌশলী শুরুতেই নকশায় কলাম আর বিম রাখেন — ঠেকার দরকারই পড়ে না। পুরোনো CSS hack হলো সেই বাঁশের ঠেকা; আধুনিক CSS হলো নকশার ভেতরেই রাখা কলাম।

কোন feature এখন নিশ্চিন্তে ব্যবহার করা যায়, সেটা বোঝার জন্য এই কোর্সে বারবার Baseline শব্দটি এসেছে। মনে করিয়ে দিই: কোনো feature যেদিন Chrome, Edge, Firefox আর Safari — ডেস্কটপ ও মোবাইল দুই জায়গাতেই — সর্বশেষ সংস্করণে চলে আসে, সেদিন সে Baseline Newly available. তার ৩০ মাস পর, যখন বেশিরভাগ মানুষের ব্রাউজার আপডেট হয়ে যায়, সে Baseline Widely available. এই পর্বের feature-গুলোর অবস্থা (২০২৬ সালের হিসাবে):

FeatureBaseline অবস্থাকী বোঝায়
CSS nesting২০২৩ সালের ডিসেম্বরে Newly available; ৩০ মাসের নিয়মে ২০২৬-এর মাঝামাঝি Widely availableনতুন কাজে নিশ্চিন্তে; খুব পুরোনো ব্রাউজার লক্ষ্য হলে build টুল দিয়ে সমতল করুন
min(), max(), clamp()Widely available (২০২০ থেকে সব প্রধান ব্রাউজারে)সব জায়গায় ব্যবহার করুন
round(), mod(), rem()Newly available (২০২৪)ব্যবহার করুন, তবে না চললেও পাতা যেন ভাঙে না
Logical property (margin-inline, inline-size…)Widely availableনতুন CSS-এ ডিফল্ট হিসেবে
@layerWidely available (২০২২ সালে তিন ব্রাউজারে প্রায় একসাথে)যেকোনো মাঝারি-বড় প্রজেক্টে
inset, aspect-ratio, accent-color, color-schemeWidely availableসব জায়গায়
text-wrap: balanceNewly available (২০২৪)নিশ্চিন্তে — না চললে লেখা সাধারণভাবে ভাঙে, ক্ষতি নেই

২০১০-এর দশকে CSS-এর অভাব মেটাতে জনপ্রিয় হয়েছিল preprocessor — Sass (২০০৬ সালে Hampton Catlin-এর হাতে শুরু, পরে Natalie Weizenbaum ও Chris Eppstein-এর হাতে বেড়ে ওঠা) আর Less. এগুলো এমন এক ভাষা, যা আপনার কম্পিউটারে চালিয়ে সাধারণ CSS-এ রূপান্তর করতে হতো; nesting, variable আর হিসাব ছিল এদের মূল আকর্ষণ। পরে CSS নিজেই একে একে এগুলো আনল — custom property (পর্ব ৩৩), calc() ও clamp(), আর সবশেষে nesting. ২০২১ সাল থেকে ব্রাউজার নির্মাতারা প্রতিবছর একসাথে কিছু feature বেছে নিয়ে সব ব্রাউজারে একই রকম করার যৌথ উদ্যোগ (প্রথমে Compat 2021, পরে Interop নামে) চালাচ্ছে, যার ফলে নতুন CSS এখন আগের চেয়ে অনেক দ্রুত সব জায়গায় পৌঁছায়।

২ · Native CSS nesting — নিয়মের ভেতরে নিয়ম

একটি পণ্যের কার্ড সাজাতে আগে এভাবে লিখতে হতো — একই .card বারবার:

.card { padding: 1rem; border: 2px solid #cbd5e1; }
.card h3 { color: teal; }
.card .price { font-weight: bold; }
.card:hover { border-color: teal; }
@media (width >= 40rem) {
  .card { padding: 2rem; }
}

সমস্যা শুধু বাড়তি টাইপ করা নয়। কার্ডের নিয়মগুলো ফাইলের নানা জায়গায় ছড়িয়ে পড়ে, আর .card-এর নাম বদলাতে হলে পাঁচ জায়গায় বদলাতে হয়। CSS nesting এই সমস্যার সমাধান: সংশ্লিষ্ট নিয়মগুলো বাইরের নিয়মের ভেতরেই লেখা যায়, ঠিক যেভাবে HTML-এ element-এর ভেতরে element থাকে। নিচের এডিটরে একই কার্ড nesting দিয়ে লেখা — মাউস কার্ডের ওপর নিয়ে দেখুন:

.card {
  max-inline-size: 22rem;
  padding: 1rem;
  border: 2px solid #cbd5e1;
  border-radius: 12px;
  font-family: sans-serif;

  h3 { margin-block: 0 .5rem; color: teal; }
  .price { font-size: 1.5rem; font-weight: bold; }
  & a { color: #be123c; }
  &:hover { border-color: teal; background: #f0fdfa; }

  @media (width >= 30rem) {
    padding: 1.5rem;
  }
}

কার্ডটি দেখতে আগের সমতল CSS-এর মতোই — শিরোনাম teal, দাম বড় আর মোটা, লিংক লাল। মাউস কার্ডের ওপর নিলে পুরো কার্ডের বর্ডার আর পেছনের রং বদলায়। ভেতরের @media-তে কোনো selector নেই, তবু padding কার্ডেই লাগছে। চেষ্টা করুন: &:hover-কে লিখুন & :hover (মাঝে একটি ফাঁকা জায়গা) — এবার শিরোনাম বা অনুচ্ছেদের ওপর মাউস নিলে শুধু সেই ছোট অংশটির রং বদলায়, পুরো কার্ডের নয়। তারপর 30rem-কে 90rem করে দেখুন — প্রিভিউ এর চেয়ে সরু, তাই padding ছোট হয়ে যায়।

নিয়মগুলো এক নজরে

  • & — nesting selector: বাইরের নিয়মের selector-কে বোঝায়। &:hover মানে .card:hover; &.sold মানে .card.sold (একই element-এ দুটি class).
  • & ছাড়া লিখলে ভেতরের element ধরা হয়: h3 {…} আর & h3 {…} একই — দুটোই .card h3.
  • & শেষে বসালে উল্টো সম্পর্ক: .theme-dark & {…} মানে “.theme-dark-এর ভেতরে থাকা .card” — কার্ডের নিয়ম কার্ডের কাছেই থাকল, যদিও শর্তটা বাইরের।
  • At-rule-ও ভেতরে বসে: @media, @supports, @container (পর্ব ৩১) — ভেতরে সরাসরি declaration লিখলে সেগুলো বাইরের selector-এ লাগে।
  • যত গভীরে খুশি, কিন্তু উচিত নয়: তিন স্তরের বেশি nesting হলে selector লম্বা আর ভারী হয়, HTML-এর গঠনের সাথে CSS শক্ত করে বাঁধা পড়ে যায়।

ব্রাউজার ভেতরে ভেতরে এই nested লেখাকে কীভাবে “খুলে” পড়ে, নিচের ডায়াগ্রামে ধাপে ধাপে দেখুন। প্রতিটি ধাপে বাঁ পাশের একটি লাইন, আর ডান পাশে ব্রাউজার যে সমতুল্য selector বোঝে:

1.card {2  padding: 1rem;3  h3 { color: teal; }4  &:hover { background: #f0fdfa; }5  & :hover { outline: 1px solid; }6  .theme-dark & { background: #111; }7  @media (width >= 40rem) { padding: 2rem; }8}9#hero, .banner { & p { color: gray; } }
ব্রাউজার যা বোঝে
selector
specificity
  1. বাইরের নিয়ম। .card { padding: 1rem; } — এখানে নতুন কিছু নেই। specificity (0,1,0), অর্থাৎ একটি class.
  2. & ছাড়া ভেতরের নিয়ম। h3-এর আগে কিছু লেখা নেই, তাই ব্রাউজার ধরে নেয় সামনে একটি & আর ফাঁকা জায়গা আছে — অর্থাৎ .card h3, কার্ডের ভেতরের যেকোনো h3. ওজন বাড়ল একটি type selector-এর: (0,1,1).
  3. &:hover — লেগে থাকা &. & আর :hover-এর মাঝে ফাঁকা নেই, তাই দুটি একই element-এর শর্ত: “কার্ডটি নিজেই যখন hover অবস্থায়”। ফল .card:hover.
  4. & :hover — একটি ফাঁকা জায়গার ফারাক। এবার মাঝে ফাঁকা জায়গা, আর CSS-এ ফাঁকা জায়গা মানে descendant combinator. ফল .card :hover — “কার্ডের ভেতরের যে element-এর ওপর মাউস আছে”। specificity একই, কিন্তু অর্থ সম্পূর্ণ আলাদা। nesting-এর সবচেয়ে প্রচলিত ভুল এটিই।
  5. & শেষে। .theme-dark & খুলে হয় .theme-dark .card — অন্ধকার থিমের ভেতরে থাকা কার্ড। এভাবে একটি component-এর সব অবস্থা তার নিজের ব্লকেই রাখা যায়।
  6. ভেতরের @media. ব্রাউজার এটিকে বাইরে টেনে আনে: @media (width >= 40rem) { .card { padding: 2rem; } }. ভেতরের declaration-গুলো বাইরের selector-এ লাগে, তাই আলাদা করে .card লিখতে হয়নি।
  7. কমা-তালিকার ভেতরে nesting. বাইরে #hero, .banner — দুটি selector. ভেতরের & হয় :is(#hero, .banner), আর :is() নেয় তালিকার সবচেয়ে ভারী সদস্যের ওজন। তাই .banner-এর ভেতরের p-ও পায় id-র ওজন (1,0,1) — পরের অংশে এর ফল দেখবেন।

Sass-এর অভ্যাস এখানে চলে না। Sass-এ .card { &__title {…} } লিখলে নাম জোড়া লেগে তৈরি হয় .card__title — BEM নামকরণে (পর্ব ৪৬) খুব জনপ্রিয় কৌশল। Native CSS-এ & একটি selector, লেখার টুকরো নয়; সে নাম জোড়া দেয় না। ব্রাউজার &__title-কে পড়ে “&-এর পরে __title নামের একটি ট্যাগ” হিসেবে — কিন্তু ট্যাগের নাম selector-এর শুরুতেই বসতে পারে, &-এর পরে নয়। তাই এটি অবৈধ selector, আর পুরো নিয়মটি নীরবে বাদ পড়ে — কোনো error-ও দেখায় না। AI প্রায়ই Sass-এর এই কোড সাধারণ .css ফাইলে দিয়ে দেয়।

“Relaxed” নিয়ম — শুরুর দিকের একটি বাধা উঠে গেছে

২০২৩ সালের শুরুতে প্রথম যে ব্রাউজার সংস্করণগুলো nesting এনেছিল, সেগুলোতে ভেতরের নিয়ম কোনো অক্ষর (যেমন h3) দিয়ে শুরু করা যেত না — লিখতে হতো & h3. কারণ, color:hover জাতীয় লেখা দেখে parser প্রথমে বুঝতে পারত না এটি declaration, নাকি selector. পরে স্পেসিফিকেশন দল একটি সমাধান বের করে, আর শর্তটি তুলে দেওয়া হয়। এখনকার সব প্রধান ব্রাউজারে h3 {…} সরাসরি লেখা যায়। পুরোনো সংস্করণের কথা ভেবে কেউ কেউ এখনো & h3 লেখেন — দুটো একই অর্থ দেয়, তাই এতে ক্ষতি নেই।

পুরোনো ব্রাউজারে কী হয়? Nesting না বোঝা ব্রাউজার ভেতরের নিয়মগুলোকে ভুল declaration ভেবে বাদ দেয়, আর কখনো কখনো তার পরের declaration-ও হারায়। অর্থাৎ পুরো কার্ডের নকশা আংশিক ভেঙে যেতে পারে। আপনার দর্শকদের মধ্যে যদি সত্যিই অনেক পুরোনো ব্রাউজার থাকে, তাহলে লেখার সময় nesting ব্যবহার করুন, কিন্তু প্রকাশের আগে Lightning CSS বা PostCSS-এর মতো build টুল দিয়ে সমতল CSS বানিয়ে নিন (পর্ব ৫০)।

৩ · Nesting-এর specificity — & আসলে :is()

পর্ব ১৮-এ দেখেছেন, :is() তার তালিকার সবচেয়ে ভারী selector-এর ওজন নেয়। Nesting-এর & ঠিক এভাবেই কাজ করে: বাইরের selector-তালিকাকে সে :is(…)-এর ভেতরে মুড়ে নেয়। বাইরে একটিমাত্র selector হলে কোনো পার্থক্য চোখে পড়ে না। কিন্তু কমা দিয়ে কয়েকটি selector থাকলে, আর তার একটি id হলে, চমক অপেক্ষা করছে:

#hero, .banner {
  & p { color: slategray; font: 18px sans-serif; }
}

/* পরে লেখা, জরুরি নোটিশ লাল হওয়ার কথা */
.note { color: crimson; font-weight: bold; }

নোটিশটি মোটা হয়েছে, কিন্তু লাল হয়নি — ধূসরই রয়ে গেছে। পাতায় #hero নামে কিছুই নেই, অনুচ্ছেদগুলো আছে .banner-এর ভেতরে। তবু & p হয়েছে :is(#hero, .banner) p, যার ওজন (1,0,1); আর .note মাত্র (0,1,0). চেষ্টা করুন: প্রথম লাইনটি লিখুন :where(#hero, .banner) { — এবার ওজন (0,0,1), আর নোটিশ লাল। অথবা #hero, অংশটি মুছে দিন — তাতেও লাল হবে।

Sass একই লেখাকে অন্যভাবে খোলে: সে বানায় দুটি আলাদা selector — #hero p, .banner p. তখন .banner-এর ভেতরের অনুচ্ছেদ পায় শুধু .banner p-এর ওজন (0,1,1). উপরের উদাহরণে সেটিও .note-এর (0,1,0)-এর চেয়ে ভারী, তাই Sass-এও নোটিশ ধূসর থাকত। কিন্তু override যদি লেখা হয় .banner .note (0,2,0) — Sass-এর সমতল CSS-এ সেটি (0,1,1)-কে হারিয়ে জিতত, আর native nesting-এ (1,0,1)-এর কাছে হারে। Sass থেকে native nesting-এ সরে আসা প্রজেক্টে এ ধরনের হঠাৎ রং বদলে যাওয়ার অভিযোগ প্রায়ই আসে।

মূল ধারণা

Nesting শুধু লেখার সুবিধা নয় — প্রতিটি স্তর selector-কে একটু ভারী করে। তাই তিনটি অভ্যাস রাখুন: (১) বাইরের তালিকায় id রাখবেন না, (২) দুই-তিন স্তরের বেশি nesting করবেন না, (৩) বেস স্টাইলের তালিকা :where()-এ মুড়ে নিন। specificity কম রাখলে পরে যেকোনো একটি class দিয়েই বদলানো যায়।

আরও গভীরে: “relaxed” নিয়ম সম্ভব হয়েছে parser-এর একটি কৌশলে — নিয়মের ভেতরে কোনো লেখা অক্ষর দিয়ে শুরু হলে ব্রাউজার আগে সেটিকে declaration হিসেবে পড়ার চেষ্টা করে, ব্যর্থ হলে আবার শুরু থেকে nested rule হিসেবে পড়ে। স্পেসিফিকেশন বলে, বাইরের selector ভেতরে :is()-এর অর্থে বসে; ফলে ব্রাউজারকে Sass-এর মতো সব সম্ভাব্য জোড়া (কখনো শত শত selector) তৈরি করতে হয় না। JavaScript-এর CSSOM-এ nested rule বাইরের rule-এর cssRules-এর ভেতরে থাকে। আর nested rule-এর পরে লেখা declaration-গুলো এখন নিজের লেখার ক্রমেই থাকে — শুরুর দিকের নিয়মে এগুলোকে ব্লকের ওপরে তুলে নেওয়া হতো, যা ২০২৪ সালে সংশোধন করা হয়েছে।

৪ · min(), max(), clamp() — পর্দার সাথে মানিয়ে নেওয়া মাপ

পর্ব ২০-এ calc() দেখেছেন। তার তিন ভাই হলো CSS-এর গাণিতিক function — যারা কয়েকটি মানের মধ্যে থেকে একটি বেছে নেয়:

  • min(a, b) — দুটির মধ্যে ছোটটি। width: min(100%, 40rem) মানে “৪০rem চওড়া, কিন্তু জায়গা কম হলে পুরো চওড়া”। আগে এর জন্য দুই লাইন লাগত: width: 100%; max-width: 40rem;
  • max(a, b) — দুটির মধ্যে বড়টি। padding: max(1rem, 4vw) — বড় পর্দায় পর্দার অনুপাতে ফাঁক, কিন্তু ফোনেও অন্তত 1rem.
  • clamp(MIN, VAL, MAX) — মানটি VAL, কিন্তু MIN-এর নিচে নামবে না, MAX-এর ওপরে উঠবে না। গাণিতিকভাবে max(MIN, min(VAL, MAX)).

পোশাক কারখানার শ্রমিকের মজুরির কথা ভাবুন। মজুরি কাজের পরিমাণ অনুযায়ী বাড়ে, কিন্তু আইন অনুযায়ী একটি ন্যূনতম সীমার নিচে দেওয়া যাবে না, আর কোম্পানির নিয়মে একটি সর্বোচ্চ সীমা আছে। clamp() ঠিক এই নিয়ম: মাঝের মানটি পরিবর্তনশীল, দুই পাশে দুটি দেয়াল।

Fluid typography — পর্দার সাথে মসৃণভাবে বাড়া লেখা

পুরোনো পদ্ধতিতে শিরোনামের মাপ লাফ দিয়ে বদলাত — ফোনে 16px, ট্যাবলেটে media query দিয়ে 20px, ডেস্কটপে 24px. মাঝের পর্দায় হঠাৎ লাফ চোখে লাগত। Fluid typography-তে লেখার মাপ পর্দার চওড়ার সাথে মসৃণভাবে বাড়ে, আর clamp() দুই প্রান্তে সীমা টেনে দেয়:

font-size: clamp(1rem, 0.8rem + 1vw, 1.5rem);
কখনো 16px-এর কম নয় · মাঝে পর্দা অনুযায়ী · কখনো 24px-এর বেশি নয়

মাঝের মানটি 0.8rem + 1vw — একটি স্থির অংশ আর পর্দার চওড়ার ১%. নিচের ডায়াগ্রামে চারটি পরিস্থিতিতে ব্রাউজার কী হিসাব করে দেখুন। শেষ দুই ধাপ সবচেয়ে গুরুত্বপূর্ণ — পাঠক যখন নিজের ব্রাউজারে লেখা বড় করেন:

1html { font-size: 100%; } /* পাঠকের ডিফল্ট */2h1 {3  font-size: clamp(1rem, 0.8rem + 1vw, 1.5rem);4}5.bad { font-size: 2vw; } /* শুধু vw */
ব্রাউজারের হিসাব
viewport
1rem
পছন্দের মান
clamp-এর ফল
.bad (2vw)
  1. ৩২০px চওড়া ফোন। 0.8rem = 12.8px, 1vw = 3.2px, যোগফল 16px — ঠিক সর্বনিম্ন সীমা 1rem-এর সমান। এর চেয়ে সরু পর্দায় যোগফল 16px-এর নিচে নামত, কিন্তু clamp নামতে দেবে না। পাশে দেখুন, শুধু 2vw দেওয়া লেখা এখানে মাত্র 6.4px — পড়াই যায় না।
  2. ৭৬৮px ট্যাবলেট। 12.8 + 7.68 = 20.48px. দুই সীমার মাঝে আছে, তাই এটিই ফল। ৩২০ থেকে ১১২০px পর্যন্ত যেকোনো চওড়ায় মাপ এভাবে সরলরেখায় বাড়ে — কোনো লাফ নেই।
  3. ১৩৬৬px ল্যাপটপ। হিসাবে আসে 26.46px, কিন্তু সর্বোচ্চ 1.5rem = 24px. ফল 24px. বিশাল মনিটরেও শিরোনাম অস্বাভাবিক বড় হবে না।
  4. পাঠক ব্রাউজারের সেটিংয়ে ডিফল্ট লেখা 20px করেছেন। এখন 1rem = 20px, তাই তিনটি অংশই বড় হলো: সর্বনিম্ন 20px, স্থির অংশ 16px, সর্বোচ্চ 30px. ফল 29.66px. কিন্তু 2vw-এর হিসাবে rem নেই — সে আগের মতোই 27.32px. পাঠকের চাওয়াকে সে পুরো উপেক্ষা করল।
  5. পাঠক Ctrl + চেপে 200% zoom করেছেন। Zoom করলে CSS পিক্সেল বড় হয়, ফলে একই পর্দায় viewport মাত্র 683 CSS px. clamp-এর ফল 19.63 CSS px, যা পর্দায় দ্বিগুণ হয়ে প্রায় 39px দেখায় — আগের 24px-এর চেয়ে অনেক বড়। কিন্তু 2vw = 13.66 CSS px, দ্বিগুণ হয়ে আবার সেই 27px — zoom করেও লেখা একটুও বড় হলো না! শুধু vw-এ লেখার মাপ দেওয়া তাই accessibility-র ভুল।

Accessibility-র শর্ত: WCAG 2.2-এর 1.4.4 (Resize Text) বলে, সহায়ক প্রযুক্তি ছাড়াই পাঠক যেন লেখা 200% পর্যন্ত বড় করতে পারেন। font-size: 5vw-এর মতো শুধু viewport-এর ওপর নির্ভর করা মাপ zoom-এ বড় হয় না, পাঠকের ডিফল্ট ফন্টও মানে না — পর্ব ২০ ও ২১-এ এই সতর্কতা দেখেছেন। তাই নিয়ম: পছন্দের মানে সবসময় একটি rem অংশ রাখুন (0.8rem + 1vw), আর সর্বনিম্ন ও সর্বোচ্চ সীমাও rem-এ দিন। সর্বোচ্চ আর সর্বনিম্নের ফারাক খুব বেশি রাখবেন না — ফারাক যত বড়, vw অংশ তত ভারী, আর zoom-এর প্রভাব তত কমে।

সূত্রটি নিজে বের করুন

ধরুন আপনি চান শিরোনাম ৩২০px পর্দায় 16px আর ১১২০px পর্দায় 24px হোক, মাঝে সরলরেখায়। এটা স্কুলের সরলরেখার সমীকরণ y = mx + c:

  1. ঢাল (m): (24 − 16) ÷ (1120 − 320) = 8 ÷ 800 = 0.01 — অর্থাৎ পর্দার প্রতি 1px-এ লেখা 0.01px বাড়ে। 0.01 × 100 = 1vw.
  2. স্থির অংশ (c): 16 − 0.01 × 320 = 12.8px = 0.8rem (16px root ধরে)।
  3. সীমা: নিচে 1rem, ওপরে 1.5rem. ব্যস — clamp(1rem, 0.8rem + 1vw, 1.5rem).

অনলাইনে অনেক “clamp calculator” আছে যা এই হিসাব করে দেয়, কিন্তু সূত্রটি জানা থাকলে আপনি AI বা টুলের দেওয়া মান যাচাই করতে পারবেন। এবার নিজে দেখুন। নিচের এডিটরের ছোট স্ক্রিপ্টটি প্রিভিউর চওড়া আর দুটি শিরোনামের আসল মাপ মেপে দেখায়:

<style>
  h1 { font: bold clamp(1rem, 0.8rem + 1vw, 1.5rem) sans-serif; margin: 0 0 8px; }
  .bad { font-size: 2vw; }
  .box { inline-size: min(100%, 25rem); padding: max(8px, 2vw); background: #e0f2fe; }
  p { font: 14px monospace; }
</style>
<h1>ঈদের অফার — সব পণ্যে ৩০% ছাড়</h1>
<h1 class="bad">শুধু 2vw দেওয়া শিরোনাম</h1>
<div class="box">min() আর max() দিয়ে মাপা বাক্স</div>
<p id="out"></p>
<script>
  (function m() {
    if (!document.body.offsetWidth) return setTimeout(m, 50);
    var h = document.querySelectorAll('h1');
    document.getElementById('out').textContent = 'viewport: ' + innerWidth +
      'px | clamp: ' + getComputedStyle(h[0]).fontSize +
      ' | 2vw: ' + getComputedStyle(h[1]).fontSize;
  })();
</script>

নিচের ধূসর লাইনে প্রিভিউর চওড়া আর দুটি শিরোনামের আসল মাপ দেখবেন। ল্যাপটপে প্রিভিউ সাধারণত ৯০০–১০০০px চওড়া, তখন clamp-এর মাপ প্রায় 22px — দুই সীমার মাঝে; ফোনে প্রিভিউ সরু, তাই মাপও কম। বাক্সটি 25rem-এর বেশি চওড়া হয় না। চেষ্টা করুন: সর্বোচ্চ সীমা 1.5rem-কে 1.1rem করুন — লেখা 17.6px-এ আটকে যাবে, কারণ এখন ওপরের দেয়াল ছুঁয়েছে। তারপর ব্রাউজারে Ctrl আর + চেপে পাতা বড় করে প্রিভিউ আবার চালান (“আগের কোড” নয়, কোডে একটি ফাঁকা জায়গা দিলেই চলবে) — viewport-এর সংখ্যা কমবে, তবু পর্দায় clamp শিরোনাম বড় দেখাবে, আর 2vw শিরোনাম প্রায় একই থাকবে।

round(), mod(), rem() — ধাপে ধাপে মান

২০২৪ সালে আরও তিনটি গাণিতিক function সব প্রধান ব্রাউজারে এসেছে (Baseline Newly available). এদের বলে stepped-value function — মানকে নির্দিষ্ট ধাপে মেলায়:

/* 8px-এর গ্রিডে মেলানো: 37px হয় 40px */
.card { padding: round(nearest, 5vw, 8px); }   /* nearest | up | down | to-zero */

/* ভাগশেষ — চিহ্ন নেয় দ্বিতীয় সংখ্যার (mod), নাকি প্রথমটির (rem) */
/* mod(-7, 3) = 2      rem(-7, 3) = -1 */
.dial { rotate: mod(var(--angle), 360deg); }

round() কাজে লাগে পিক্সেলের ঝাপসা অর্ধেক মান এড়াতে আর নকশার ৪ বা ৮px গ্রিড মানতে। এগুলো নতুন, তাই এমনভাবে ব্যবহার করুন যাতে না চললেও ক্ষতি না হয় — আগের লাইনে একটি সাধারণ মান দিয়ে রাখুন (padding: 2rem; তারপর padding: round(…);). যে ব্রাউজার বোঝে না, সে দ্বিতীয় লাইন বাদ দিয়ে প্রথমটি রাখবে। পাশাপাশি sin(), cos(), pow(), sqrt()-এর মতো আরও কিছু function-ও CSS-এ আছে — গোল করে বৃত্তে জিনিস সাজানোর মতো কাজে লাগে।

৫ · Logical property — বাঁ-ডান নয়, শুরু-শেষ

পর্ব ২৩-এ দেখেছেন, CSS আসলে দুটি অক্ষ ধরে ভাবে। Inline অক্ষ — একটি লাইনের ভেতরে শব্দ যেদিকে চলে; block অক্ষ — নতুন লাইন আর নতুন block যেদিকে জমা হয়। বাংলা আর ইংরেজিতে inline অক্ষ বাঁ থেকে ডানে, block অক্ষ ওপর থেকে নিচে। উর্দু, আরবি, ফারসি বা হিব্রুতে inline অক্ষ ডান থেকে বাঁয়ে — একে বলে RTL (right-to-left). আর প্রচলিত জাপানি বইয়ে লাইন খাড়া, ওপর থেকে নিচে (writing-mode: vertical-rl).

margin-left, padding-right, top, width — এগুলো physical property: পর্দার দিক ধরে বলা। এর logical জোড়াগুলো বলে লেখার দিক ধরে — “লেখা যেখানে শুরু হয়”, “লেখা যেখানে শেষ হয়”:

পুরোনো (physical)আধুনিক (logical)বাংলা/ইংরেজি পাতায় মানে
margin-top / margin-bottommargin-block-start / margin-block-endওপরে / নিচে
margin-left / margin-rightmargin-inline-start / margin-inline-endবাঁয়ে / ডানে (RTL-এ উল্টো)
বাঁ-ডান দুটোই একসাথেmargin-inline: auto, padding-inline: 1remদুই পাশে
ওপর-নিচ দুটোই একসাথেpadding-block: 1remওপরে ও নিচে
border-leftborder-inline-startলেখা শুরুর দিকের বর্ডার
top, leftinset-block-start, inset-inline-startpositioned element-এর অবস্থান (পর্ব ২৪)
width / heightinline-size / block-sizeচওড়া / উচ্চতা
max-widthmax-inline-sizeসর্বোচ্চ চওড়া
border-top-left-radiusborder-start-start-radiusওপর-বাঁ কোণ (আগে block, পরে inline)
text-align: lefttext-align: startলেখা শুরুর দিকে সাজানো

কোণের নামটি একটু ধাঁধার মতো: border-start-start-radius-এর প্রথম “start” block অক্ষের (ওপর), দ্বিতীয়টি inline অক্ষের (বাংলায় বাঁ)। তাই বাংলা পাতায় এটি ওপর-বাঁ কোণ, উর্দু পাতায় ওপর-ডান কোণ। একই CSS তিনটি ভিন্ন লেখার দিকে কী হয়ে যায়, ডায়াগ্রামে দেখুন:

1.note {2  margin-inline-start: 2rem;3  border-inline-end: 4px solid;4  padding-block: 1rem;5  inline-size: 20rem;6}
পর্দার কোন দিকে পড়ল
লেখা
margin-inline-start
border-inline-end
padding-block
inline-size
  1. বাংলা বা ইংরেজি পাতা। লেখা বাঁ থেকে ডানে, লাইন জমে ওপর থেকে নিচে। তাই inline-start = বাঁ, inline-end = ডান, block = ওপর-নিচ। ফল হুবহু margin-left, border-right, padding-top/bottom, width-এর মতো।
  2. উর্দু বা আরবি পাতা (dir="rtl"). লেখা শুরু হয় ডান থেকে, তাই inline-start এখন ডানে — margin চলে গেল ডানে, বর্ডার বাঁয়ে। block অক্ষ বদলায়নি। একটি লাইন CSS-ও না বদলে নকশা আয়নার মতো উল্টে গেল — ঠিক যা উর্দু পাঠক আশা করেন।
  3. খাড়া জাপানি লেখা (writing-mode: vertical-rl). লাইন ওপর থেকে নিচে চলে, আর লাইনগুলো জমে ডান থেকে বাঁয়ে। তাই inline-start = ওপরে, inline-end = নিচে, block অক্ষ আনুভূমিক, আর inline-size এখন উচ্চতা। Physical property দিয়ে লিখলে এখানে পুরো CSS নতুন করে লিখতে হতো।

এবার আসল ব্রাউজারে দেখুন। নিচে একই দুটি নোটিশ-বক্স — একটি physical, একটি logical property দিয়ে — প্রথমে বাংলা পাতায়, তারপর dir="rtl" দেওয়া উর্দু অংশে। মনে রাখুন, লেখার দিক জানানোর কাজ HTML-এর dir attribute-এর (পর্ব ১৪), CSS-এর নয়:

<style>
  .box { position: relative; margin-block: 14px; background: #f1f5f9; font: 18px sans-serif; }
  .old { border-left: 6px solid #dc2626; padding-left: 12px; text-align: left;
         border-top-left-radius: 14px; }
  .new { border-inline-start: 6px solid #16a34a; padding-inline-start: 12px;
         text-align: start; border-start-start-radius: 14px; }
  .tag { position: absolute; inset-block-start: -10px; inset-inline-end: 10px;
         padding-inline: 8px; background: #fde047; font-size: 13px; }
</style>
<div lang="bn">
  <p class="box old">স্বাগতম — physical</p>
  <p class="box new">স্বাগতম — logical <span class="tag">নতুন</span></p>
</div>
<div dir="rtl" lang="ur">
  <p class="box old">خوش آمدید — physical</p>
  <p class="box new">خوش آمدید — logical <span class="tag">نیا</span></p>
</div>

বাংলা অংশে দুটি বক্স দেখতে প্রায় একই — রঙিন দাগ বাঁয়ে, গোল কোণ ওপর-বাঁয়ে। উর্দু অংশে পার্থক্য স্পষ্ট: লাল (physical) বক্সের দাগ আর লেখা দুটোই এখনো বাঁ দিকে আটকে আছে, অথচ উর্দু পাঠকের চোখ লাইনের শুরু খোঁজে ডান দিকে — নকশাটি তাই উল্টো দেখায়। সবুজ (logical) বক্সে দাগ, ফাঁক আর গোল কোণ সব ডান দিকে চলে গেছে, আর হলুদ “نیا” ব্যাজ বসেছে বাঁ কোণে — inset-inline-end মানে লেখা শেষের দিক। চেষ্টা করুন: প্রথম div-এও dir="rtl" দিন — বাংলা লেখাও ডান দিকে সরে যাবে। তারপর .new-এ writing-mode: vertical-rl; যোগ করে দেখুন দাগ কোথায় যায়।

সব কিছু logical করার দরকার নেই। যা পর্দার সাথে বাঁধা, লেখার সাথে নয়, তা physical-ই থাকবে — যেমন আলো ওপর-বাঁ থেকে আসছে বোঝাতে box-shadow-এর offset, কোনো ছবির নির্দিষ্ট দিক, বা পর্দার ডান কোণে স্থির একটি চ্যাট-বোতাম, যা আপনি সব ভাষায় ডানেই চান। অন্যদিকে তীর-চিহ্ন (← →) প্রায়ই লেখার দিক অনুযায়ী উল্টাতে হয় — “পরের পাতা” উর্দুতে বাঁ দিকে। প্রশ্নটা হলো: এই দিকটি কি লেখার প্রবাহের অংশ? হ্যাঁ হলে logical.

৬ · Cascade layers গভীরে — CSS-এর তাক সাজানো

পর্ব ১৮-এ cascade-এর সিঁড়িতে দেখেছেন, layer-এর ধাপ আসে specificity-র আগে। অর্থাৎ কোন layer-এ নিয়মটি আছে, সেটা ঠিক হয়ে গেলে selector কত ভারী, তা আর বিবেচ্য নয়। এখন দেখব এই ক্ষমতা দিয়ে একটি পুরো প্রজেক্ট কীভাবে সাজানো যায়।

একটি সুপারশপের তাকের কথা ভাবুন। নিচের তাকে সাধারণ মাল (reset), তার ওপরে কোম্পানির সরবরাহ করা প্যাকেট (framework), তার ওপরে দোকানের নিজের সাজানো জিনিস (components), আর একদম ওপরে আজকের বিশেষ ছাড়ের স্টিকার (utilities). ক্রেতার চোখে প্রথমে পড়ে ওপরের তাক — জিনিসটি কত দামি বা বড়, তা দেখে নয়, কোন তাকে রাখা হয়েছে তা দেখে। দোকানের ম্যানেজার শুরুতেই ঠিক করে দেন কোন তাক কোথায় থাকবে।

প্রথম লাইন: ক্রম ঘোষণা

Layer-এর ক্রম ঠিক হয় প্রথমবার যখন তার নাম দেখা যায়। তাই ভালো অভ্যাস হলো ফাইলের একদম শুরুতে একটি ক্রম ঘোষণা লেখা — তারপর যেকোনো জায়গায়, যেকোনো ক্রমে layer-এ নিয়ম যোগ করুন, ক্রম বদলাবে না:

/* সবার আগে — দুর্বল থেকে শক্ত */
@layer reset, framework, components, utilities;

/* বাইরের library-কে নিজের একটি layer-এ ঢোকানো */
@import url("bootstrap.min.css") layer(framework);

@layer components {
  .btn { background: teal; }
}
@layer reset {               /* পরে লেখা, তবু সবচেয়ে দুর্বল */
  *, *::before, *::after { box-sizing: border-box; }
}

কয়েকটি নিয়ম মনে রাখুন। @import থাকতে হবে ফাইলের ওপরে — তার আগে শুধু @charset আর @layer-এর ক্রম ঘোষণা বসতে পারে, অন্য কোনো নিয়মের পরে লিখলে ব্রাউজার @import-টি পুরো বাদ দেয়। HTML-এর <link> ট্যাগে layer দেওয়ার কোনো প্রচলিত উপায় এখনো নেই, তাই বাইরের CSS-কে layer-এ নিতে হলে একটি CSS ফাইলের ভেতরে (বা <style>-এ) এভাবে @import করুন। আর মনে রাখুন, @import একটির পর একটি ফাইল নামায় বলে পাতা দেরিতে দেখা দিতে পারে — পারফরম্যান্সের দিক পর্ব ৪৯-এ।

নাম ছাড়া @layer { … } লিখলে তৈরি হয় নামহীন (anonymous) layer, যাতে পরে আর কিছু যোগ করা যায় না। আর layer-এর ভেতরে layer-ও রাখা যায়: @layer framework.base মানে framework-এর ভেতরের base উপ-তাক।

তিনটি সোনালি নিয়ম

নিচের ডায়াগ্রামে একটি বোতামের পেছনের রং নিয়ে লড়াই — পাঁচটি জায়গা থেকে পাঁচটি প্রার্থী। ধাপে ধাপে দেখুন কে জেতে, আর কেন:

  1. resetbuttonসবচেয়ে নিচের তাক
  2. framework#buy.btnবাইরের library
  3. components.btnনিজের নকশা
  4. utilities.bg-redছোট সাহায্যকারী
  5. layer-এর বাইরে.btnunlayered
ধাপ ১ক্রম ঘোষণা
@layer reset, framework, components, utilities;
/* বাঁ থেকে ডানে: দুর্বল → শক্ত */
ধাপ ২পরের layer জেতে — specificity যা-ই হোক
@layer framework  { #buy.btn { background: gray; } }  (1,1,0)
@layer components { .btn { background: teal; } }     (0,1,0) ← জিতল
ধাপ ৩layer-এর বাইরের CSS সবার ওপরে
@layer utilities { .bg-red { background: red; } }
.btn { background: purple; }   ← জিতল: কোনো layer-এ নেই
ধাপ ৪!important-এ ক্রম উল্টে যায়
@layer framework  { .btn { background: gray !important; } } ← জিতল
@layer components { .btn { background: teal !important; } }
.btn { background: purple !important; }   সবচেয়ে দুর্বল important
ধাপ ৫পুরো ছবি — দুর্বল থেকে শক্ত
reset → framework → components → utilities → layer-এর বাইরে
  … সাধারণ declaration শেষ, এবার !important …
layer-এর বাইরে! → utilities! → components! → framework! → reset!
  1. ক্রম ঘোষণা। একটি লাইনেই চারটি তাকের জায়গা ঠিক হলো — বাঁ দিকের reset সবচেয়ে দুর্বল, ডানের utilities সবচেয়ে শক্ত। ফাইলে পরে কোন layer আগে লেখা হলো, তাতে আর কিছু যায় আসে না।
  2. সোনালি নিয়ম ১ — পরের layer জেতে। framework-এর #buy.btn-এ id আছে, ওজন (1,1,0); components-এর .btn মাত্র (0,1,0). তবু teal জিতল, কারণ components পরে ঘোষিত। specificity-র তুলনা হয় শুধু একই layer-এর ভেতরে। বাইরের library-র ভারী selector হারাতে আর কোনো কসরত লাগে না।
  3. সোনালি নিয়ম ২ — layer-এর বাইরের CSS সব layer-কে হারায়। কোনো @layer-এ না রাখা নিয়মকে বলে unlayered — সে যেন সবার শেষের একটি অদৃশ্য layer. এতে পুরোনো CSS ভাঙে না, আর দ্রুত একটি ঠিক করা সহজ হয়। কিন্তু উল্টো বিপদও আছে: আপনি নিজের CSS layer-এ রাখলেন আর কোনো library-কে বাইরে ফেলে রাখলেন — তাহলে library-ই জিতবে! তাই বাইরের CSS-কেও layer-এ ঢোকান।
  4. সোনালি নিয়ম ৩ — !important-এ ক্রম উল্টো। পর্ব ১৮-এ দেখেছেন, important-এ উৎসের ক্রম উল্টে যায়; layer-এও তাই। এখন আগে ঘোষিত layer-এর important জেতে, আর layer-এর বাইরের important সবচেয়ে দুর্বল। যুক্তি: নিচের তাক যদি বলে “এটা না হলেই নয়” (যেমন reset-এ [hidden] { display: none !important; }), ওপরের তাক যেন সেটা ভুল করে মুছে দিতে না পারে।
  5. পুরো ছবি। সাধারণ declaration-এ বাঁ থেকে ডানে শক্তি বাড়ে, শেষে layer-এর বাইরের CSS. তার ওপরে সব important, কিন্তু উল্টো ক্রমে — সবচেয়ে শক্ত reset-এর important. (আর HTML-এর style attribute? Cascade-এ layer-এর ধাপের আগেই সে বিচার পায়, তাই সাধারণ অবস্থায় সব layer-কে হারায়।)

এবার নিজের হাতে লড়াইটা চালান। ধরা যাক framework layer-টি বাইরের একটি library:

@layer reset, framework, components, utilities;

@layer components {
  .btn { background: teal; color: white; padding: 10px 18px;
         border: 0; border-radius: 8px; font-size: 18px; }
}
@layer framework {           /* ধরুন বাইরের library */
  #buy.btn { background: gray; }
}
@layer utilities {
  .btn { outline: 3px dashed orange; outline-offset: 3px; }
}
@layer reset {
  button { font-family: inherit; }
}

বোতামটি teal রঙের, চারপাশে কমলা ড্যাশ দাগ। framework-এর id-ওয়ালা ভারী selector হেরেছে, কারণ components পরে ঘোষিত। চেষ্টা করুন: একদম শেষে layer-এর বাইরে লিখুন .btn { background: purple; } — বোতাম বেগুনি হবে। তারপর সেটি মুছে প্রথম লাইনে framework আর components-এর জায়গা বদলে দিন — এবার ধূসর জিতবে। আর প্রথম লাইনটিই মুছে দিলে? তখন ক্রম ঠিক হবে নাম প্রথম দেখার ক্রমে: components, framework, utilities, reset — ধূসর।

এবার !important-এর উল্টো ক্রম পরীক্ষা করুন। তিন জায়গাতেই important — কে জিতবে, প্রিভিউ দেখার আগে অনুমান করুন:

@layer framework, components;

@layer framework {
  .btn { background: gray !important; }
}
@layer components {
  .btn { background: teal !important; }
}

/* layer-এর বাইরে */
.btn { background: purple !important; color: white;
       padding: 10px 18px; border: 0; border-radius: 8px; font-size: 18px; }

বোতামটি ধূসর! সাধারণ নিয়মে যে framework সবচেয়ে দুর্বল, important-এ সে-ই সবচেয়ে শক্ত, আর layer-এর বাইরের important সবচেয়ে দুর্বল। চেষ্টা করুন: framework-এর !important মুছে দিন — এবার teal (components-এর important). তারপর components-এর !important-ও মুছুন — এবার বেগুনি, কারণ এখন শুধু বাইরের নিয়মে important বাকি। এই উল্টো ক্রমের কারণেই layer দিয়ে গোছানো প্রজেক্টে !important প্রায় দরকারই হয় না।

revert-layer আর DevTools

পর্ব ১৮-এর CSS-wide keyword-এর তালিকায় revert-layer দেখেছিলেন। কোনো property-তে এটি দিলে বর্তমান layer-এর মান বাদ পড়ে, আগের layer-এর মান ফিরে আসে — যেমন একটি component-এ library-র আসল বোতামের নকশা ফেরত আনা। আর Chrome বা Edge-এর DevTools-এ Styles প্যানেলে প্রতিটি নিয়মের পাশে তার layer-এর নাম দেখা যায়; “Elements” প্যানেলে layer-এর পুরো ক্রমও দেখানোর ব্যবস্থা আছে। কোনো নিয়ম কেন হারল, layer-এর নাম দেখেই প্রায়ই বোঝা যায়।

Layerভেতরে কী রাখবেনকেন এই জায়গায়
resetbox-sizing, margin শূন্য করা, img { max-inline-size: 100% }সবচেয়ে সাধারণ, সবাই বদলাতে পারে
basebody-র ফন্ট, লিংকের রং, heading-এর মাপসাইটের মৌলিক চেহারা
frameworkBootstrap-জাতীয় বাইরের CSSনিজের নকশা যেন সহজে জেতে
componentsকার্ড, বোতাম, navbarপ্রজেক্টের মূল নকশা
utilities.visually-hidden, .text-centerএক জায়গায় ছোট পরিবর্তন — সবার ওপরে থাকা দরকার

৭ · :is() আর :where() — হালকা selector-এর কৌশল

Layer ঠিক করে কোন দল নিয়ম কোন দলের ওপরে। কিন্তু একই layer-এর ভেতরে আবার specificity-র লড়াই চলে। সেখানে সবচেয়ে কার্যকর অস্ত্র :where() — যার ওজন সবসময় শূন্য। আধুনিক reset আর library-গুলো এই কারণেই তাদের নিয়ম :where()-এ মুড়ে লেখে:

@layer reset {
  /* ওজন শূন্য — যেকোনো class সহজে বদলাতে পারে */
  :where(ul, ol)[role="list"] { list-style: none; padding: 0; }
  :where(h1, h2, h3) { line-height: 1.2; text-wrap: balance; }
}

@layer components {
  /* :is() দিয়ে পুনরাবৃত্তি কমানো — এখানে ভারী হওয়াই চাই */
  .card :is(h2, h3):hover { color: teal; }
}

কৌশলটির নাম দেওয়া যায় “specificity-র বাজেট”: বেশিরভাগ selector (0,1,0) বা তার কম রাখুন — অর্থাৎ একটি class. id দিয়ে স্টাইল দেবেন না (id রাখুন লিংকের লক্ষ্য আর JavaScript-এর জন্য)। বেস স্টাইল :where()-এ, component একটি class-এ, অবস্থা (:hover, .is-open) তার সাথে একটি বাড়তি অংশে। তাহলে কোন নিয়ম জিতবে, তা বোঝা যায় শুধু layer আর লেখার ক্রম দেখে। আরেকটি সুবিধা: :is() আর :where()-এর তালিকা forgiving — তালিকার একটি selector ব্রাউজারের অচেনা হলেও বাকিগুলো কাজ করে, যেখানে সাধারণ কমা-তালিকায় একটি ভুলেই পুরো নিয়ম বাদ পড়ে।

৮ · ছোট ছোট আধুনিক সুবিধা — inset, aspect-ratio, accent-color, color-scheme

বড় feature-এর পাশে কিছু ছোট property আছে, যারা প্রতিদিনের কাজ অনেক সহজ করেছে। এর কয়েকটি আগের পর্বে ছুঁয়ে গেছেন; এখানে এক জায়গায়:

  • inset — top, right, bottom, left একসাথে (পর্ব ২৪)। inset: 0 মানে পুরো parent ঢেকে দেওয়া — আগে চার লাইন লাগত।
  • aspect-ratio — চওড়া আর উচ্চতার অনুপাত (পর্ব ১৯)। আগে ভিডিওর বাক্স ১৬:৯ রাখতে লেখা হতো padding-top: 56.25% — কারণ শতাংশে দেওয়া padding চওড়ার হিসাবে হয়, আর ৯ ÷ ১৬ = ০.৫৬২৫. এখন শুধু aspect-ratio: 16 / 9.
  • text-wrap: balance — শিরোনামের লাইনগুলো প্রায় সমান দৈর্ঘ্যের করে (পর্ব ২১)। আগে হাতে <br> দিয়ে বা JavaScript library দিয়ে এই কাজ করা হতো। text-wrap: pretty অনুচ্ছেদের শেষ লাইনে একা একটি শব্দ পড়ে থাকা এড়ায়, তবে এর সাপোর্ট এখনো সব ব্রাউজারে সমান নয় — Can I use দেখে নিন।
<style>
  .thumb { position: relative; inline-size: min(100%, 320px); aspect-ratio: 16 / 9;
           background: linear-gradient(135deg, #0ea5e9, #6366f1); border-radius: 10px; }
  .thumb span { position: absolute; inset: auto 0 0 0; padding: 6px 10px;
                background: rgb(0 0 0 / .6); color: white; font: 14px sans-serif; }
  .play { position: absolute; inset: 0; margin: auto; inline-size: 56px; aspect-ratio: 1;
          border-radius: 50%; background: white; }
  h2 { inline-size: min(100%, 380px); font: bold 26px sans-serif; text-wrap: balance; }
</style>
<div class="thumb">
  <div class="play"></div>
  <span>ভিডিও: ঢাকা মেট্রো রেলের প্রথম দিন</span>
</div>
<h2>অনলাইন দোকান খোলার আগে যে পাঁচটি জিনিস জানা দরকার</h2>

নীল-বেগুনি ভিডিও-বাক্সটি ঠিক ১৬:৯, নিচে কালো ফিতায় শিরোনাম (inset: auto 0 0 0 — ওপর ছাড়া তিন দিকে লেগে আছে)। মাঝখানে সাদা গোল বোতাম — inset: 0 আর margin: auto মিলে নিখুঁত মাঝখানে, আর aspect-ratio: 1 তাকে বর্গ রেখেছে। নিচের শিরোনামের দুই লাইন প্রায় সমান লম্বা। চেষ্টা করুন: 16 / 9-কে 4 / 3 বা 1 করুন; তারপর text-wrap: balance মুছে দেখুন — দ্বিতীয় লাইনে একা একটি-দুটি শব্দ পড়ে থাকতে পারে।

accent-color আর color-scheme — ফর্ম কন্ট্রোলের রং আর আলো-অন্ধকার

Checkbox, radio আর range slider-এর রং বদলাতে আগে পুরো কন্ট্রোল লুকিয়ে নতুন করে আঁকতে হতো — অনেক CSS, আর প্রায়ই keyboard বা screen reader-এর সমস্যা। accent-color এক লাইনে ব্রাউজারের নিজের কন্ট্রোলে আপনার ব্র্যান্ডের রং বসায়, কন্ট্রোলের আচরণ একটুও না বদলে। আর color-scheme ব্রাউজারকে জানায় এই অংশটি হালকা না অন্ধকার নকশার — তাহলে ব্রাউজার নিজের ফর্ম কন্ট্রোল, scrollbar আর system color (Canvas = পেছনের রং, CanvasText = লেখার রং) সেই অনুযায়ী আঁকে:

<style>
  .wrap { display: flex; flex-wrap: wrap; gap: 12px; font: 16px sans-serif; }
  .panel { padding: 12px 16px; border-radius: 10px; accent-color: #16a34a;
           background: Canvas; color: CanvasText; border: 1px solid gray; }
  .light { color-scheme: light; }
  .dark  { color-scheme: dark; }
</style>
<div class="wrap">
  <div class="panel light">
    <p><label><input type="checkbox" checked> SMS-এ জানান</label></p>
    <p><label><input type="radio" name="p1" checked> bKash</label></p>
    <p><input type="range" aria-label="পরিমাণ" value="70"></p>
  </div>
  <div class="panel dark">
    <p><label><input type="checkbox" checked> SMS-এ জানান</label></p>
    <p><label><input type="radio" name="p2" checked> bKash</label></p>
    <p><input type="range" aria-label="পরিমাণ" value="70"></p>
  </div>
</div>

দুটি প্যানেলে একই কন্ট্রোল — checkbox, radio আর slider সবুজ রঙে, কারণ accent-color. বাঁয়েরটি সাদা পেছনে কালো লেখা, ডানেরটি অন্ধকার পেছনে সাদা লেখা — অথচ আমরা কোনো রং নিজে লিখিনি, শুধু Canvas আর CanvasText চেয়েছি, আর color-scheme বলে দিয়েছে কোনটি হালকা, কোনটি অন্ধকার। চেষ্টা করুন: #16a34a-কে crimson করুন; তারপর .light-এর মান light dark করে দেখুন — আপনার কম্পিউটারের থিম অন্ধকার হলে সেটিও অন্ধকার হবে।

color-scheme হলো dark mode-এর প্রথম ধাপ মাত্র। পাতার রং দুই থিমে আলাদা করার পুরো কৌশল — prefers-color-scheme, light-dark() function আর custom property দিয়ে থিম — আসছে পরের পর্বে।

৯ · আধুনিক CSS চিটশিট — পুরোনো কৌশল থেকে নতুন feature

পুরোনো কোড মেরামত, AI-এর দেওয়া কোড যাচাই, বা ইন্টারভিউ — সব জায়গায় কাজে লাগবে এই তালিকা। বাঁ কলামের কিছু দেখলে বুঝবেন কোডটি পুরোনো ধারার, আর ডান কলামে তার আধুনিক বিকল্প:

পুরোনো কৌশল (hack)আধুনিক featureপর্ব
Nesting-এর জন্য Sass/Less compile করাNative CSS nesting৩৫
Sass variable $brandCustom property --brand (চলার সময় বদলানো যায়)৩৩
width: 100%; max-width: 40rem;width: min(100%, 40rem)৩৫
প্রতি breakpoint-এ আলাদা font-sizeclamp(1rem, 0.8rem + 1vw, 1.5rem)৩৫
[dir="rtl"]-এর জন্য উল্টো CSS কপিLogical property (margin-inline-start…)৩৫
!important-এর যুদ্ধ, #id#id বা .a.a দিয়ে ওজন বাড়ানো@layer আর :where()১৮, ৩৫
top: 0; right: 0; bottom: 0; left: 0;inset: 0২৪
padding-top: 56.25% দিয়ে ১৬:৯ বাক্সaspect-ratio: 16 / 9১৯
Checkbox লুকিয়ে নতুন করে আঁকাaccent-color (পুরো নতুন নকশা দরকার হলে পর্ব ৪৩)৪৩
শিরোনামে হাতে <br> বা JavaScripttext-wrap: balance২১
Clearfix (::after { clear: both })display: flow-root২৩
Float বা table দিয়ে পাতার লেআউটFlexbox আর Grid২৫–২৯
অন্ধকার থিমে সাদা ফর্ম কন্ট্রোল আর scrollbarcolor-scheme: light dark৩৫, ৩৬
-webkit-, -moz- prefix সব জায়গায়বেশিরভাগ ক্ষেত্রে আর দরকার নেই; বাকিটা build টুলের কাজ৪৮, ৫০
JavaScript দিয়ে parent-কে স্টাইল দেওয়া:has()৩৪
পর্দার মাপ দেখে component-এর নকশাContainer query৩১

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

  • clamp(a, b, c) কী করে? — মান b নেয়, কিন্তু a-এর নিচে বা c-এর ওপরে যেতে দেয় না; সমান max(a, min(b, c)).
  • Native nesting-এ &:hover আর & :hover-এর পার্থক্য? — প্রথমটি element নিজেই hover অবস্থায়; দ্বিতীয়টি তার ভেতরের hover করা যেকোনো element.
  • Unlayered CSS আর layer-এর CSS-এ কে জেতে? — সাধারণ declaration-এ unlayered; !important-এ layer-এর (আগে ঘোষিত layer সবচেয়ে শক্ত)।
  • margin-inline-start কেন margin-left-এর চেয়ে ভালো? — লেখার দিক মানে; RTL বা খাড়া লেখায় নিজে থেকে ঠিক দিকে বসে।
  • শুধু vw-এ font-size দিলে সমস্যা কী? — zoom বা ব্রাউজারের ফন্ট সেটিংয়ে লেখা বড় হয় না, WCAG-এর Resize Text শর্ত ভাঙে; rem মেশাতে হয়।

১০ · AI-এর যুগে — পুরোনো CSS চিনে ফেলার চোখ

AI-এর শেখার উপাদানের বড় অংশ গত পনেরো-বিশ বছরের ওয়েব। সেখানে পুরোনো CSS-এর পরিমাণ নতুন CSS-এর চেয়ে অনেক বেশি — তাই AI প্রায়ই চলনসই কিন্তু পুরোনো ধারার কোড দেয়, আর কখনো কখনো বলে বসে “native CSS-এ nesting নেই, Sass লাগবে” — যা এখন আর সত্য নয়। উপরের চিটশিটটি এখানেই কাজে লাগবে।

AI যা প্রায়ই দেয়সমস্যাযা চাইবেন
.css ফাইলে &__title, $color, @mixinএগুলো Sass — ব্রাউজারে নীরবে কাজ করে নাNative nesting আর custom property
font-size: 4vwzoom-এ বড় হয় নাrem মেশানো clamp()
সব জায়গায় margin-left, padding-rightRTL-এ ভাঙেLogical property
Bootstrap-কে হারাতে একের পর এক !importantimportant-যুদ্ধBootstrap-কে @layer-এ ঢোকানো
চার-পাঁচ স্তরের গভীর nestingভারী selector, HTML-এর সাথে শক্ত বাঁধনবড়জোর দুই-তিন স্তর
@import অন্য নিয়মের পরেব্রাউজার পুরো @import বাদ দেয়ফাইলের একদম ওপরে

AI-কে কাজ দেওয়ার সময়ই বলে দিন কোন ধারার CSS চান, আর কোন ব্রাউজার লক্ষ্য। তাহলে পুরোনো hack অনেক কম আসবে, আর যা আসবে তার কারণও জানতে পারবেন:

একটি পণ্যের কার্ডের CSS লিখে দাও। শর্ত: ১. শুধু সাধারণ .css ফাইলে চলে এমন আধুনিক CSS — Sass-এর syntax (&__, $variable, @mixin) দেবে না। ২. Native nesting ব্যবহার করো, কিন্তু দুই স্তরের বেশি নয়; কোনো id selector দিয়ে স্টাইল দেবে না। ৩. সব দিক logical property-তে (margin-inline, padding-block, inline-size) — পাতাটি উর্দুতেও চলবে। ৪. শিরোনামের font-size clamp() দিয়ে, যার সব অংশে rem আছে; কেন এই মান, সেই হিসাবও দেখাও। ৫. সব নিয়ম @layer components-এর ভেতরে। ৬. যে feature এখনো Baseline Widely available নয়, তার পাশে মন্তব্যে লেখো, আর না চললে কী হবে বলো।

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

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Sass-এ nesting ছিল ২০০৬ সাল থেকে। তাহলে CSS-এ নিজের nesting আসতে প্রায় ১৭ বছর লাগল কেন?

মূল কারণ parsing — ব্রাউজার CSS-এর লেখা কীভাবে পড়ে। Sass চলে ডেভেলপারের কম্পিউটারে, একবার; সে পুরো ফাইল আগে পড়ে নিয়ে ধীরেসুস্থে বুঝতে পারে কোনটি কী। ব্রাউজারকে কিন্তু প্রতিটি পাতা খোলার সময় কোটি কোটি ডিভাইসে খুব দ্রুত CSS পড়তে হয়, আর CSS-এর parser ঐতিহ্যগতভাবে খুব সরল: একটি নিয়মের ভেতরে যা আছে, তা declaration — property: value;.

সমস্যাটা দেখুন: নিয়মের ভেতরে a:hover { … } লেখা থাকলে, শুরুর a:hover দেখতে হুবহু একটি declaration-এর মতো — property “a”, মান “hover”! কোনটি তা বোঝার জন্য parser-কে সামনে অনেক দূর পর্যন্ত পড়তে হতো (যাকে বলে unbounded lookahead), যা ব্রাউজার নির্মাতারা পারফরম্যান্সের কারণে চাইতেন না। তাই বহু বছর ধরে নানা প্রস্তাব এসেছে — প্রতিটি nested নিয়মের আগে @nest লেখা, অথবা সবসময় & দিয়ে শুরু করা।

২০২১-২২ সালে CSS Working Group এই কাজে নতুন করে গতি আনে। কোন syntax সবচেয়ে স্বাভাবিক লাগে, তা নিয়ে ২০২২ সালের শেষ দিকে ব্রাউজার দলগুলো সাধারণ ডেভেলপারদের কাছে খোলাখুলি মতামতও চেয়েছিল। ২০২৩ সালে প্রথম সংস্করণ আসে সেই শর্ত নিয়ে — ভেতরের নিয়ম অক্ষর দিয়ে শুরু করা যাবে না। কিছুদিনের মধ্যে প্রকৌশলীরা দেখালেন, “আগে declaration হিসেবে পড়ার চেষ্টা, ব্যর্থ হলে নিয়ম হিসেবে” — এই কৌশলে খরচ সামান্য, তাই শর্তটি তুলে দেওয়া হলো। ফলে Sass-এর প্রায় সব পরিচিত লেখা এখন সরাসরি চলে।

আরেকটি কারণ ছিল সিদ্ধান্তের দায়। ওয়েবে একবার কোনো syntax চালু হলে আর ফেরানো যায় না — কোটি পাতা তার ওপর নির্ভর করতে শুরু করে। Sass ভুল করলে পরের সংস্করণে বদলাতে পারে; ব্রাউজার পারে না। তাই সময় নেওয়াটাই ছিল দায়িত্বশীলতা।

মূল উপলব্ধি: ওয়েব প্ল্যাটফর্মে “চিরকাল চলতে হবে” — এই শর্তই প্রতিটি নতুন feature-কে ধীর কিন্তু সাবধানী করে। একটি টুল (Sass) যেখানে পরীক্ষা চালায়, প্ল্যাটফর্ম পরে সবচেয়ে ভালো ধারণাটি স্থায়ী করে।

প্র ০২Fluid typography-তে শুধু vw ব্যবহার করলে কী ক্ষতি? একজন দৃষ্টিপ্রতিবন্ধী পাঠকের দিক থেকে ভাবুন।

ধরুন রাজশাহীর একজন অবসরপ্রাপ্ত শিক্ষক চোখে কম দেখেন। তিনি ব্রাউজারের সেটিংয়ে ডিফল্ট লেখা বড় করে রেখেছেন, আর তবু ছোট লেখা দেখলে Ctrl + চেপে পাতা zoom করেন। এখন যে সাইটের শিরোনাম font-size: 4vw, সেখানে কী হয়?

  • ডিফল্ট ফন্টের সেটিং কাজ করে না: সেটিং বদলায় শুধু rem-এর মান (আর তার থেকে আসা em). vw হিসাব হয় পর্দার চওড়া থেকে, তাই তাঁর সেটিং পুরো উপেক্ষিত।
  • Zoom-ও কাজ করে না: Zoom করলে CSS পিক্সেল বড় হয়, ফলে viewport-এর CSS-পিক্সেলে মাপ ছোট হয়ে যায়। 4vw তখন আগের চেয়ে কম CSS পিক্সেল, যা বড় পিক্সেলে আঁকা হয় — দুটো কাটাকাটি হয়ে পর্দায় লেখা প্রায় একই মাপে থাকে। অন্য সব লেখা বড় হয়, শুধু শিরোনাম জমে থাকে।

WCAG 2.2-এর সাফল্যের শর্ত 1.4.4 Resize Text (স্তর AA) চায়, লেখা 200% পর্যন্ত বড় করা যাক কোনো তথ্য বা কাজ না হারিয়ে। শুধু vw-এর লেখা এই শর্ত ভাঙতে পারে — আর অনেক দেশে সরকারি সাইট ও বড় প্রতিষ্ঠানের সাইটের জন্য AA স্তর আইন বা নীতির অংশ।

সমাধান সহজ: clamp(1rem, 0.8rem + 1vw, 1.5rem)-এর মতো সূত্রে rem অংশ থাকায় zoom আর ফন্ট সেটিং দুটোই কাজ করে। তবু সূক্ষ্ম একটি ব্যাপার আছে — vw অংশ যত বড়, zoom-এর প্রভাব তত কম। সর্বোচ্চ সীমা যদি সর্বনিম্নের তিন-চার গুণ হয়, তাহলে বড় পর্দায় 200% zoom-এ লেখা দ্বিগুণ না-ও হতে পারে। তাই অভিজ্ঞ ডিজাইনাররা সীমা দুটির ফারাক পরিমিত রাখেন, আর ব্রাউজারে নিজে zoom করে যাচাই করেন — শুধু হিসাবের ওপর ভরসা করেন না।

মূল উপলব্ধি: “পর্দার সাথে মানানো” আর “পাঠকের সাথে মানানো” এক কথা নয়। ভালো CSS দুটোকেই সম্মান করে — vw পর্দার কথা শোনে, rem পাঠকের।

প্র ০৩বাংলাদেশের বেশিরভাগ সাইট তো বাংলা আর ইংরেজিতে — দুটোই বাঁ থেকে ডানে। তাহলে logical property শেখার দরকার কী?

প্রথম কারণ বাজার। বাংলাদেশের অনেক ফ্রিল্যান্সার আর সফটওয়্যার প্রতিষ্ঠান মধ্যপ্রাচ্যের গ্রাহকদের জন্য কাজ করেন — সৌদি আরব, আরব আমিরাত, কাতারের ই-কমার্স বা সরকারি সেবার সাইটে আরবি ভাষা বাধ্যতামূলক। পাকিস্তানের উর্দু, ইরানের ফারসি, ইসরায়েলের হিব্রু — সবই ডান থেকে বাঁয়ে। বিশ্বের কয়েকশো কোটি মানুষ এমন লিপিতে পড়েন। একটি সাইট “আরবিতেও চালু করুন” — এই অনুরোধ এলে physical property দিয়ে লেখা CSS-এর প্রায় প্রতিটি margin, padding, border, position উল্টে লিখতে হয়।

Logical property আসার আগে বড় প্রতিষ্ঠানগুলো এই সমস্যা সামলাত বিশেষ টুল দিয়ে — CSSJanus বা RTLCSS-এর মতো প্রোগ্রাম পুরো CSS ফাইল পড়ে left-কে right বানিয়ে আলাদা একটি RTL ফাইল তৈরি করত। দুটি ফাইল রক্ষণাবেক্ষণ, ভুল উল্টানো (যেমন যে ছায়া উল্টানো উচিত ছিল না), আর ব্যতিক্রম চিহ্নিত করার জন্য বিশেষ মন্তব্য — ঝামেলা কম নয়।

দ্বিতীয় কারণ একই পাতায় দুই দিক। একটি বাংলা সংবাদ সাইটে আরবি উদ্ধৃতি, একটি ইসলামি শিক্ষার সাইটে কুরআনের আয়াত, বা একটি মেসেজিং অ্যাপে কেউ উর্দুতে লিখলেন — এসব জায়গায় শুধু ওই অংশে dir="rtl" দিলেই logical CSS নিজে ঠিক হয়ে যায়।

তৃতীয় কারণ অভ্যাস আর আধুনিক লেআউটের ভাষা। Flexbox আর Grid আগে থেকেই start-end দিয়ে ভাবে (justify-content: start). Logical property শিখলে পুরো CSS এক ভাষায় চলে। ব্রাউজারের নিজের user-agent stylesheet-ও এখন margin-block-start-এর মতো logical নামে লেখা — DevTools খুললেই দেখবেন (পর্ব ১৬)। আর খরচ? প্রায় শূন্য — নাম একটু লম্বা, কিন্তু বাংলা পাতায় ফল হুবহু এক।

মূল উপলব্ধি: “বাঁ-ডান” পর্দার ভাষা, “শুরু-শেষ” লেখার ভাষা। আজকের কোড লেখার ভাষায় লিখলে কাল নতুন বাজারে যেতে নতুন করে লিখতে হয় না।

প্র ০৪Specificity তো ছিলই — তাহলে @layer-এর দরকার পড়ল কেন? আর layer-এর বাইরের CSS কেন সবাইকে হারায়, উল্টোটা কেন নয়?

Specificity একটি selector-এর গঠন দেখে ওজন মাপে — কয়টি id, কয়টি class. কিন্তু বড় প্রজেক্টে আসল প্রশ্ন গঠনের নয়, উদ্দেশ্যের: এই নিয়মটি কি reset, নাকি library, নাকি আমার component, নাকি এক জায়গার জরুরি পরিবর্তন? Specificity এই উদ্দেশ্য বোঝে না। ফল হলো চেনা দৃশ্য — Bootstrap-এর একটি বোতামের selector .btn-group > .btn:not(:last-child), তাকে হারাতে আপনি লিখলেন আরও লম্বা selector, তারপর আরেকজন আরও লম্বা, শেষে !important. একে বলা হয় specificity war.

সমাধান হিসেবে BEM-এর মতো নামকরণের নিয়ম (পর্ব ৪৬) এসেছিল — সব selector-কে একটি class-এ রাখার শৃঙ্খলা। কিন্তু শৃঙ্খলা মানুষের ওপর নির্ভর করে, আর বাইরের library আপনার শৃঙ্খলা মানে না। Cascade Layers-এর স্পেসিফিকেশন (CSS Cascading and Inheritance Level 5; সম্পাদকদের একজন Miriam Suzanne) সমস্যাটিকে ভাষার ভেতরে এনে দিল: উদ্দেশ্য অনুযায়ী তাক, আর তাকের ক্রম specificity-র আগে।

এবার দ্বিতীয় প্রশ্ন। Unlayered CSS সবার ওপরে রাখার পেছনে দুটি যুক্তি। এক, পুরোনো কোডের নিরাপত্তা: ২০২২-এর আগে লেখা প্রতিটি CSS ফাইলই unlayered. কেউ নিজের নতুন কোড বা একটি library-কে layer-এ রাখলে পুরোনো কোডের আচরণ বদলায় না — সে আগের মতোই জেতে। দুই, ধাপে ধাপে গ্রহণ: একটি বড় প্রজেক্ট আগে library-কে layer-এ ঢোকাতে পারে, বাকি কোড ছুঁয়েও দেখতে হয় না — নিজের সব CSS নিজে থেকেই library-কে হারাবে।

আর !important-এ উল্টো ক্রমের যুক্তি পর্ব ১৮-এর user বনাম author-এর মতোই: যে স্তর সাধারণভাবে দুর্বল, তার জরুরি ঘোষণাকে রক্ষা করা। Reset যদি বলে [hidden] কখনো দেখা যাবে না, কোনো component ভুল করে display: flex দিয়ে সেটা ভাঙতে পারবে না।

মূল উপলব্ধি: ভালো নকশা কৌশলকে শৃঙ্খলার ওপর ছেড়ে না দিয়ে ব্যবস্থার ভেতরে বসায়। @layer CSS-কে “কে বেশি ভারী” থেকে “কে কোন দায়িত্বে” — এই প্রশ্নে নিয়ে এসেছে।

প্র ০৫Nesting, variable, হিসাব — সবই এখন CSS-এ। তাহলে কি Sass আর build টুল অপ্রয়োজনীয় হয়ে গেল?

অনেক ছোট-মাঝারি প্রজেক্টের জন্য উত্তর এখন “প্রায় হ্যাঁ”। একটি ব্যক্তিগত পোর্টফোলিও, একটি রেস্তোরাঁর সাইট বা একটি ছোট অনলাইন দোকানের CSS-এ আজ Sass-এর সবচেয়ে জনপ্রিয় তিনটি কাজ — nesting, variable আর হিসাব — সাধারণ CSS-এই হয়। Custom property আবার Sass variable-এর চেয়েও শক্তিশালী, কারণ সে ব্রাউজারে বেঁচে থাকে: JavaScript বা media query দিয়ে চলার সময় বদলানো যায়, যা compile-এর সময় হারিয়ে যাওয়া Sass variable পারে না।

কিন্তু Sass-এর আরও কিছু ক্ষমতা আছে, যা CSS-এ এখনো সাধারণভাবে নেই বা সবে আসছে:

  • Mixin আর function: একগুচ্ছ declaration-কে নাম দিয়ে বারবার বসানো, প্যারামিটারসহ। CSS Working Group-এ নিজস্ব function আর mixin-এর প্রস্তাব নিয়ে কাজ চলছে, কিছু ব্রাউজার পরীক্ষামূলকভাবে আনছে — কিন্তু এখনো Baseline নয়।
  • Loop আর শর্ত: @each, @for দিয়ে ১০টি রঙের জন্য ১০টি utility class তৈরি করা।
  • ফাইল ভাগ করে একটিতে জোড়া: ২০টি ছোট ফাইল লিখে প্রকাশের সময় একটি ফাইল বানানো, যাতে ব্রাউজারকে ২০টি অনুরোধ করতে না হয়।

আর build টুলের কাজ শুধু Sass নয়। Lightning CSS বা PostCSS-এর মতো টুল CSS ছোট (minify) করে, পুরোনো ব্রাউজারের জন্য nesting খুলে সমতল করে, প্রয়োজন হলে prefix যোগ করে। অর্থাৎ ভবিষ্যতের পথ সম্ভবত এমন: আপনি লিখবেন একদম আদর্শ, আধুনিক CSS, আর টুল শুধু প্রকাশের আগে তাকে ছোট আর বেশি ব্রাউজারের উপযোগী করবে — নতুন ভাষা শেখাবে না। পুরো আলোচনা পর্ব ৫০-এ।

মূল উপলব্ধি: ওয়েবে বারবার একই চক্র ঘটে — টুল একটি অভাব পূরণ করে, জনপ্রিয় হয়, তারপর প্ল্যাটফর্ম সেই ধারণা নিজের ভেতরে নেয় (jQuery থেকে querySelector, Sass থেকে nesting). তাই টুল শিখুন, কিন্তু ভিত্তি গড়ুন প্ল্যাটফর্মের ওপর — সেটাই সবচেয়ে বেশি দিন টেকে।

অনুশীলন

উপরের লাইভ এডিটরগুলোতেই এগুলো পরীক্ষা করে দেখতে পারবেন।

  1. Sass থেকে native: একজন সহকর্মী Sass থেকে কপি করে style.css-এ লিখেছেন: .menu { &__item { padding: 8px; } &--active { color: red; } a:hover { color: blue; } }. কোন অংশগুলো ব্রাউজারে কাজ করবে, কোনগুলো করবে না, আর কীভাবে ঠিক করবেন?

    a:hover { … } ঠিকমতো কাজ করবে — এটি .menu a:hover. কিন্তু &__item আর &--active কাজ করবে না: native CSS-এ & নাম জোড়া দেয় না; ব্রাউজার &__item-কে পড়ে “&-এর পরে একটি ট্যাগের নাম” হিসেবে, যা অবৈধ selector — তাই নিয়ম দুটি নীরবে বাদ পড়ে, কোনো error-ও দেখায় না। ঠিক করার উপায়: BEM-এর পুরো নাম আলাদা নিয়মে লিখুন (.menu__item { padding: 8px; }), অথবা class-এর গঠন বদলে .menu { .item {…} &.active {…} } লিখুন। আর যদি Sass-ই ব্যবহার করতে চান, ফাইলটির নাম .scss রেখে compile করুন।

  2. clamp() হিসাব করুন: font-size: clamp(1.25rem, 1rem + 2vw, 2.5rem) (root 16px). ৪০০px, ১০০০px আর ১৬০০px চওড়া পর্দায় লেখার মাপ কত? কোন চওড়ায় মাপ ওপরের সীমা ছোঁয়?

    সীমা: সর্বনিম্ন 20px, সর্বোচ্চ 40px; পছন্দের মান = 16px + চওড়ার ২%.

    • ৪০০px: 16 + 8 = 24px — সীমার ভেতরে, ফল 24px.
    • ১০০০px: 16 + 20 = 36px.
    • ১৬০০px: 16 + 32 = 48px, কিন্তু সর্বোচ্চ 40px — ফল 40px.

    ওপরের সীমা ছোঁয় যখন 16 + 0.02 × w = 40, অর্থাৎ w = ১২০০px. আর নিচের সীমা ছোঁয় w = ২০০px-এ, যা প্রায় কোনো পর্দাই নয়। এখানে সর্বোচ্চ সর্বনিম্নের দ্বিগুণ, আর vw অংশও বেশ ভারী (2vw) — তাই এই শিরোনাম 200% zoom-এ ঠিকমতো বড় হয় কি না, ব্রাউজারে যাচাই করা উচিত।

  3. Logical-এ রূপান্তর: নিচের CSS-কে logical property-তে লিখুন, যাতে উর্দু পাতাতেও ঠিক থাকে: .alert { width: 30rem; margin-left: auto; margin-right: auto; padding-left: 1rem; border-left: 5px solid; text-align: left; } আর .close { position: absolute; top: 8px; right: 8px; }
    .alert {
      inline-size: 30rem;
      margin-inline: auto;
      padding-inline-start: 1rem;
      border-inline-start: 5px solid;
      text-align: start;
    }
    .close { position: absolute; inset-block-start: 8px; inset-inline-end: 8px; }

    margin-left আর margin-right দুটোই auto ছিল, তাই একটি margin-inline: auto যথেষ্ট। বন্ধ করার বোতাম ছিল লেখা শেষের কোণে (ডানে), তাই inset-inline-end — উর্দু পাতায় সেটি নিজে থেকে বাঁ কোণে যাবে, যেখানে উর্দু পাঠক বন্ধ করার বোতাম খোঁজেন।

  4. Layer-এর পরিকল্পনা: আপনার সাইটে Bootstrap-এর CSS আছে, আর নিজের বোতামের রং কিছুতেই বসছে না — আগের ডেভেলপার ১২টি !important লিখে রেখেছেন। একটি layer কাঠামো প্রস্তাব করুন: ফাইলের ওপরের লাইনগুলো কী হবে, আর !important-গুলোর কী করবেন?
    @layer reset, framework, base, components, utilities;
    @import url("bootstrap.min.css") layer(framework);

    Bootstrap এখন framework layer-এ, তাই components-এ লেখা সাধারণ .btn { … }-ও তার ভারী selector-কে হারাবে — specificity-র তুলনাই হবে না। এরপর নিজের !important-গুলো এক এক করে মুছে দেখুন; বেশিরভাগই আর দরকার হবে না। সাবধান: যেগুলো রেখে দেবেন, layer-এর ভেতরে important-এর ক্রম উল্টো — framework-এর কোনো important থাকলে সেটি components-এর important-কে হারাবে। Bootstrap নিজে কিছু utility class-এ !important ব্যবহার করে, তাই সেগুলোর সাথে লড়াই না করে নিজের utility নিজের layer-এ লিখুন। শেষে DevTools-এ দেখুন, বোতামের রং কোন layer থেকে আসছে।

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

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

ক্রম@layer reset, framework, components; ঘোষিত পাতায় সাধারণ (important নয়) declaration-গুলোকে দুর্বল থেকে শক্ত ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🧹 reset layer-এর নিয়ম
  2. 📦 framework layer-এর নিয়ম
  3. 🧩 components layer-এর নিয়ম
  4. 🚪 কোনো layer-এ না থাকা নিয়ম
  5. 📌 HTML-এর style attribute

Native CSS nesting-এ .card { & :hover { … } } কোন element-কে ধরে?

  • কার্ডটি নিজে, যখন তার ওপর মাউস আছে
  • কার্ডের ভেতরের যে element-এর ওপর মাউস আছে
  • কার্ডের parent element, যখন hover অবস্থায়
  • কোনোটিই নয় — এটি অবৈধ, ব্রাউজার বাদ দেয়

Root font-size 16px. font-size: clamp(1rem, 0.8rem + 1vw, 1.5rem) — ৭৬৮px চওড়া viewport-এ লেখার মাপ কত?

  • 16px
  • 24px
  • 20.48px
  • 7.68px

@layer base, theme; ঘোষিত পাতায় base-এ #logo { color: red; }, theme-এ .brand { color: green; }, আর কোনো layer-এর বাইরে a { color: blue; }. <a id="logo" class="brand">-এর রং কী?

  • নীল
  • লাল
  • সবুজ
  • ব্রাউজারের ডিফল্ট লিংকের রং

@layer framework, components; — দুই layer-এই .btn { background: … !important; } আছে, আর layer-এর বাইরেও একটি !important আছে। কোনটি জেতে?

  • layer-এর বাইরের নিয়ম
  • components-এর নিয়ম
  • যেটি ফাইলে সবশেষে লেখা
  • framework-এর নিয়ম

dir="rtl" দেওয়া একটি উর্দু অনুচ্ছেদে margin-inline-start: 2rem পর্দার কোন দিকে ফাঁক তৈরি করে?

  • বাঁ দিকে
  • ডান দিকে
  • ওপরে
  • দুই পাশে সমান

নিচের কোনটি শিরোনামের font-size হিসেবে accessibility-র দিক থেকে সবচেয়ে ভালো?

  • font-size: 4vw
  • font-size: 28px
  • font-size: clamp(1.25rem, 1rem + 1vw, 1.75rem)
  • font-size: clamp(1rem, 3vw, 3rem)

Native CSS nesting-এ #hero, .banner { & p { color: gray; } } — .banner-এর ভেতরের p-তে এই নিয়মের specificity কত?

  • (1, 0, 1)
  • (0, 1, 1)
  • (0, 0, 1)
  • (1, 1, 1)

শব্দকোষ

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

মনে রাখুন

  1. Native nesting-এ & = বাইরের selector, specificity :is()-এর মতো; &:hover ≠ & :hover, আর Sass-এর &__ নাম-জোড়া চলে না।
  2. clamp(MIN, VAL, MAX) দিয়ে fluid typography; মাঝের মান rem + vw, কখনো শুধু vw নয় — zoom আর পাঠকের ফন্ট সেটিং যেন কাজ করে।
  3. Logical property লেখার দিক মানে: inline = লাইনের দিক, block = লাইন জমার দিক; dir="rtl"-এ নিজে থেকে উল্টায়।
  4. @layer: শুরুতে ক্রম ঘোষণা; পরের layer জেতে; unlayered সবার ওপরে; !important-এ ক্রম উল্টো; বাইরের CSS @import … layer()-এ।
  5. ছোট সুবিধা: inset, aspect-ratio, accent-color, color-scheme, text-wrap: balance — পুরোনো hack দেখলে চিটশিট মিলিয়ে আধুনিক বিকল্প বসান।

পরের পর্বে: আজ color-scheme দিয়ে ব্রাউজারকে আলো-অন্ধকারের কথা জানালেন। এবার পুরো পাতাকে দুই থিমে সাজাব — prefers-color-scheme, light-dark(), custom property দিয়ে থিম, আর পাঠক নিজে থিম বেছে নিলে কী করবেন।

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

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