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

Sass আর PostCSS — CSS-এর সহকারী টুল

Sass & PostCSS — Preprocessors, Postprocessors and the CSS Build Step

Taught in Bangla, this lesson explains preprocessors and postprocessors as build-time tools, then covers Dart Sass variables, nesting, partials with @use and @forward, mixins, functions, @extend, loops, conditions and maps with their compiled output, what modern CSS now replaces, PostCSS plugins such as Autoprefixer, postcss-preset-env and cssnano, Lightning CSS, and a small npm, VS Code and source map setup.

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

  • Preprocessor আর postprocessor কোথায় বসে, আর কেন এরা “build-এর সময়ের” টুল
  • Sass-এর মূল feature — variable, nesting, partial, @use/@forward, mixin, function, @extend, loop আর map — আর প্রতিটির compile করা ফল
  • কোন কাজ এখন সাধারণ CSS-ই করে, আর কোথায় Sass এখনো সত্যিই সময় বাঁচায়
  • PostCSS-এর plugin-শিকল: Autoprefixer, browserslist, postcss-preset-env, cssnano; Tailwind আর Lightning CSS-এর সাথে সম্পর্ক
  • npm script, VS Code আর source map দিয়ে একটি ছোট, পেশাদার CSS build সেটআপ — আর AI-এর দেওয়া পুরোনো Sass কোড চেনার উপায়

১ · সহকারী টুল কী — ব্রাউজারের আগে আর পরে

মিরপুরের একটি অনলাইন পোশাকের দোকানের সাইট বানাচ্ছেন নাদিয়া। CSS ফাইল এখন দুই হাজার লাইনের। দোকানের মূল রং teal — সেটি লেখা আছে ষাট জায়গায়। প্রতিটি বোতামের জন্য একই পাঁচ লাইন বারবার কপি করা। আর পুরোনো iPhone-এ কিছু নকশা ঠিক দেখায় না, কারণ সেখানে কোনো কোনো property-র আগে -webkit- লাগত। নাদিয়া ভাবছেন — এগুলো হাতে হাতে সামলানোর কি কোনো সহজ উপায় নেই?

উপায় আছে, আর সেটি দুই রকম টুল। দুটিই চলে আপনার কম্পিউটারে, পাতা প্রকাশের আগে — এই ধাপকে বলে build step.

  • Preprocessor (যেমন Sass): CSS-এর চেয়ে একটু বেশি ক্ষমতার একটি ভাষায় আপনি লেখেন — variable, loop, mixin. টুলটি সেটি পড়ে সাধারণ CSS বানায়। অর্থাৎ CSS তৈরির আগের ধাপ।
  • Postprocessor (যেমন PostCSS): হাতে নেয় একটি তৈরি CSS ফাইল, আর তাকে বদলায় — prefix বসায়, নতুন syntax পুরোনো ব্রাউজারের উপযোগী করে, ফাইল ছোট করে। অর্থাৎ CSS তৈরির পরের ধাপ।

CSS-এর build পাইপলাইন

From the file you write to the file the browser downloads

  1. আপনি লেখেনmain.scssvariable, mixin, loop
  2. Sassmain.csspreprocessor
  3. PostCSSmain.min.csspostprocessor
  4. ব্রাউজার<link>শুধু সাধারণ CSS দেখে
ছবি ৫০.১ — দুটি টুলই ব্রাউজারের বাইরে কাজ করে; ব্রাউজারের কাছে পৌঁছায় শুধু শেষ ফাইলটি।

একটি গার্মেন্টস কারখানার কথা ভাবুন। ডিজাইনার খাতায় সংক্ষেপে লেখেন: “সব শার্টে কোম্পানির রং, কলারে একই সেলাই”। কাটিং সেকশন সেই সংক্ষিপ্ত নির্দেশ থেকে আসল টুকরো কাটে — এটাই preprocessor. তারপর ফিনিশিং ও প্যাকিং সেকশন: যে দেশে চালান যাবে সেই দেশের লেবেল লাগায়, ভাঁজ করে, ছোট প্যাকেটে ভরে — এটাই postprocessor. ক্রেতা (ব্রাউজার) হাতে পান শুধু তৈরি শার্ট; ডিজাইনারের খাতা তিনি কখনো দেখেন না।

মূল ধারণা

Sass বা PostCSS ব্রাউজারকে নতুন কোনো ক্ষমতা দেয় না। চূড়ান্ত ফাইলে যা আছে, তা আপনি নিজেও হাতে লিখতে পারতেন। এরা শুধু লেখার কাজ সহজ করে আর ভুলের সুযোগ কমায়। তাই একটি প্রশ্ন সবসময় কাজে লাগে: “এই কোডের compile করা CSS দেখতে কেমন হবে?” — এই পর্বের প্রতিটি উদাহরণে সেটিই পাশাপাশি দেখাব।

২০০৬ সালে Hampton Catlin Sass-এর ধারণা দেন, আর Natalie Weizenbaum এর নকশা ও প্রথম সংস্করণ (Ruby ভাষায়) গড়ে তোলেন; পরে Chris Eppstein মূল দলে যোগ দেন। শুরুর Sass ছিল বন্ধনী আর সেমিকোলন ছাড়া, indent-নির্ভর ভাষা (.sass ফাইল)। ২০০৯ সালে Alexis Sellier আনেন Less, যা দেখতে হুবহু CSS-এর মতো — আর ২০১০ সালে TJ Holowaychuk আনেন Stylus. প্রতিযোগিতার চাপে ২০১০ সালে Sass 3 নিয়ে আসে SCSS syntax (.scss), যেখানে যেকোনো বৈধ CSS-ই বৈধ SCSS. আজ প্রায় সবাই SCSS লেখেন। Ruby Sass-এর মেয়াদ শেষ হয় ২০১৯ সালে; C++-এ লেখা দ্রুত সংস্করণ LibSass (আর তার ওপর বানানো node-sass) ২০২০ সালে deprecated ঘোষিত হয়। এখন একমাত্র সক্রিয় সংস্করণ Dart Sass — npm-এ যার নাম শুধু sass.

২ · Sass variable — compile হলেই উধাও

Sass-এর প্রথম আকর্ষণ ছিল variable. নাম শুরু হয় $ দিয়ে: $brand: #0f766e;. তারপর যেখানে রংটি লাগবে, সেখানে লিখুন $brand. বাঁ পাশে আপনি যা লেখেন, ডান পাশে Sass যা বানায়:

ইনপুট · offer.scss

$brand: #0f766e;
$radius: 10px;
$space: 1rem;

.offer {
  padding: $space;
  border: 2px solid $brand;
  border-radius: $radius;
}
.offer-btn {
  background: $brand;
  border-radius: $radius;
}

আউটপুট · offer.css

.offer {
  padding: 1rem;
  border: 2px solid #0f766e;
  border-radius: 10px;
}
.offer-btn {
  background: #0f766e;
  border-radius: 10px;
}

খেয়াল করুন, আউটপুটে কোনো $ নেই — variable-এর ঘোষণার লাইনগুলো পুরো উধাও, আর প্রতিটি ব্যবহারের জায়গায় মানটি বসে গেছে। ব্রাউজার যে ফাইল পায়, সেটিই নিচের এডিটরে (সাথে কয়েকটি সাজানোর লাইন)। এটি বদলে দেখুন:

/* Sass-এর তৈরি CSS — এখানে আর কোনো variable নেই */
.offer {
  font-family: sans-serif;
  max-width: 22rem;
  padding: 1rem;
  border: 2px solid #0f766e;
  border-radius: 10px;
}
.offer-btn {
  background: #0f766e;
  color: white;
  border: 0;
  padding: .5rem 1rem;
  border-radius: 10px;
}

teal বর্ডারের একটি অফার-কার্ড আর teal বোতাম দেখছেন। চেষ্টা করুন: শুধু .offer-এর #0f766e-কে crimson করুন — বর্ডার লাল হলো, কিন্তু বোতাম teal-ই রইল। তৈরি CSS-এ দুটি রং এখন আর একে অপরকে চেনে না; একসাথে বদলাতে হলে মূল .scss বদলে আবার compile করতে হয়। এবার বোতামের লাইনে লিখুন background: $brand; — বোতামের রং পুরো চলে যায়, কারণ ব্রাউজারের কাছে $brand একটি অচেনা মান, তাই সে declaration-টি বাদ দেয়।

Sass variable বনাম CSS custom property

পর্ব ৩৩-এ CSS custom property (--brand) বিস্তারিত দেখেছেন। দুটির পার্থক্য মনে করিয়ে দিই — এক কথায়, Sass variable ছাঁচ, custom property জীবন্ত:

বিষয়Sass $brandCSS --brand
মান কখন বসেbuild-এর সময়, একবারব্রাউজারে, পাতা চলার সময়, যতবার দরকার
:hover, media query, থিমে বদলনাহ্যাঁ
JavaScript বা DevTools দিয়ে বদলনাহ্যাঁ
media query-র শর্তে ব্যবহার, যেমন (width >= $bp)হ্যাঁনা
ভুল নাম লিখলেcompile-এর সময়ই error: Undefined variableনীরবে fallback বা ভুল মান

শেষ সারিটি অবহেলা করবেন না — Sass ভুল নাম ধরিয়ে দেয়, ব্রাউজার দেয় না। এবার “জীবন্ত” কথাটির মানে নিজে দেখুন। একই কার্ড, কিন্তু রং রাখা হয়েছে custom property-তে:

.offer {
  --brand: #0f766e;
  font-family: sans-serif;
  max-width: 22rem;
  padding: 1rem;
  border: 2px solid var(--brand);
  border-radius: 10px;
}
.offer:hover { --brand: #be123c; }

.offer-btn {
  background: var(--brand);
  color: white;
  border: 0;
  padding: .5rem 1rem;
  border-radius: 10px;
}
@media (width < 20rem) {
  .offer { --brand: #7c3aed; }
}

মাউস কার্ডের ওপর নিলেই বর্ডার আর বোতাম একসাথে লাল হয় — একটিমাত্র লাইন --brand বদলেছে, ব্রাউজার পাতা চলার সময়ই হিসাব নতুন করেছে। Sass-এ এমনটি অসম্ভব, কারণ ব্রাউজারের কাছে কোনো $brand পৌঁছায়ই না। চেষ্টা করুন: শেষ media query-র 20rem-কে 90rem করুন — প্রিভিউ এর চেয়ে সরু, তাই কার্ড বেগুনি হয়ে যায়।

Sass ভেতরে আসলে কী করে, সেটি বুঝতে একটি খুদে “Sass variable compiler” নিজে চালিয়ে দেখুন। মাত্র দুটি regular expression দিয়ে এটি variable-এর ঘোষণা মনে রাখে, মুছে দেয়, আর ব্যবহারের জায়গায় মান বসায়:

const scss = `$brand: teal;
$gap: 12px;
.btn { color: $brand; padding: $gap; }
.card { border: 2px solid $brand; }`;

const vars = {};
const out = scss
  // ধাপ ১: "$নাম: মান;" লাইন মনে রাখো, আউটপুট থেকে মুছে দাও
  .replace(/^\s*\$([\w-]+)\s*:\s*([^;]+);\s*$/gm, (all, name, value) => {
    vars[name] = value.trim();
    return '';
  })
  // ধাপ ২: বাকি প্রতিটি $নাম-এর জায়গায় তার মান বসাও
  .replace(/\$([\w-]+)/g, (all, name) => {
    if (!(name in vars)) throw new Error('Undefined variable: $' + name);
    return vars[name];
  })
  .trim();

console.log(JSON.stringify(vars));
console.log(out);

প্রথম লাইনে Sass-এর “খাতা” — {"brand":"teal","gap":"12px"}; তারপর তৈরি CSS, যেখানে কোনো $ নেই। চেষ্টা করুন: .card-এর লাইনে $brand-কে $brnd লিখুন — আসল Sass-এর মতোই Undefined variable error আসে। আসল Sass অবশ্যই regex নয়; সে পুরো ফাইলকে parse করে, scope, module আর হিসাব বোঝে — কিন্তু মূল ভাবনা এটুকুই: লেখা বদলে আরেকটি লেখা।

৩ · Nesting — Sass-এর আর ব্রাউজারের

Sass-এর দ্বিতীয় বড় আকর্ষণ ছিল nesting: সংশ্লিষ্ট নিয়ম বাইরের নিয়মের ভেতরে লেখা, & মানে বাইরের selector. পর্ব ৩৫-এ দেখেছেন, এখন native CSS nesting ব্রাউজারেই চলে (Baseline Newly available ২০২৩ সালের ডিসেম্বরে, ৩০ মাসের নিয়মে ২০২৬-এর মাঝামাঝি থেকে Widely available). দেখতে প্রায় এক, কিন্তু দুটি জায়গায় ফল আলাদা:

ইনপুট · card.scss

.card {
  padding: 1rem;
  &:hover { outline: 2px solid; }
  &__title { font-weight: 700; }
}
#hero, .banner {
  p { color: gray; }
}

Sass-এর আউটপুট

.card {
  padding: 1rem;
}
.card:hover {
  outline: 2px solid;
}
.card__title {
  font-weight: 700;
}
#hero p, .banner p {
  color: gray;
}
  • নাম জোড়া লাগানো: Sass-এ &__title লেখা হিসেবে জোড়া লেগে হয় .card__title — BEM নামকরণে (পর্ব ৪৬) খুব জনপ্রিয় কৌশল। Native CSS-এ & একটি selector, লেখার টুকরো নয়; সেখানে &__title কিছুই ধরে না, আর কোনো error-ও দেখায় না।
  • Specificity: Sass কমা-তালিকা খুলে দুটি আলাদা selector বানায় — .banner p-এর ওজন (0,1,1). ব্রাউজারের native nesting বাইরের তালিকাকে :is(#hero, .banner) হিসেবে ধরে, তাই .banner-এর ভেতরের p-ও id-র ওজন (1,0,1) পায়। Sass থেকে native nesting-এ সরে আসলে কোনো কোনো রং হঠাৎ বদলে যায় এই কারণেই।

গভীর nesting-এর ফাঁদ। Sass-এ চার-পাঁচ স্তর nesting লেখা খুব সহজ, তাই প্রায়ই তৈরি হয় .page .main .content .card .title span-এর মতো লম্বা, ভারী selector. এতে specificity বাড়ে, HTML-এর গঠন বদলালেই CSS ভাঙে, ফাইলও বড় হয়। ভালো নিয়ম: দুই, খুব বেশি হলে তিন স্তর — Sass হোক বা native.

৪ · Partial আর module system — @use ও @forward

বড় প্রজেক্টে সব CSS এক ফাইলে রাখলে খুঁজে পাওয়া কঠিন। Sass ফাইল ভাগ করতে দেয়: রঙের জন্য একটি, বোতামের জন্য একটি, লেআউটের জন্য একটি। যে ফাইলের নাম _ দিয়ে শুরু (যেমন _tokens.scss), সেটি partial — Sass তাকে আলাদা CSS ফাইল বানায় না, অন্য ফাইল টেনে নিলে তবেই কাজে লাগে।

ফাইল টানার আধুনিক নিয়ম @use. আগে সবাই লিখতেন @import, কিন্তু তার তিনটি বড় সমস্যা ছিল: সব variable সব ফাইলে global হয়ে যেত (কোন $brand কোথা থেকে এলো বোঝা কঠিন), একই ফাইল কয়েকবার import হলে তার CSS-ও কয়েকবার আউটপুটে আসত, আর দুই library-র একই নামের mixin একে অপরকে চাপা দিত। ২০১৯ সালে আসে module system; ২০২৪ সালে Dart Sass 1.80 থেকে Sass-এর @import আনুষ্ঠানিকভাবে deprecated — ব্যবহার করলে সতর্কবার্তা আসে, আর Dart Sass 3.0-তে এটি সরিয়ে ফেলা হবে।

নিচের ডায়াগ্রামে একটি ছোট প্রজেক্টের চারটি ফাইল ধাপে ধাপে দেখুন — শেষে কোন CSS তৈরি হয়:

  1. _tokens.scss$brandরং আর মাপ
  2. _mixins.scss@use "tokens"mixin
  3. _index.scss@forwardএক প্রবেশপথ
  4. main.scss@use … as aআসল নিয়ম
  5. main.css.offerশুধু এটিই প্রকাশ
abstracts/_tokens.scssনামের শুরুতে _ = partial
$brand: #0f766e !default;
$radius: 10px !default;
$-secret: 3px;   // - দিয়ে শুরু = private, বাইরে দেখা যায় না
abstracts/_mixins.scssনিজের যা লাগে, নিজেই টানে
@use "tokens";

@mixin card {
  border: 2px solid tokens.$brand;
  border-radius: tokens.$radius;
}
abstracts/_index.scssদুই ফাইলের সদস্য একসাথে পাঠায়
@forward "tokens";
@forward "mixins";
main.scssফোল্ডারের নাম দিলেই _index পড়ে
@use "abstracts" as a;

.offer {
  @include a.card;
  color: a.$brand;
}
main.csssass main.scss main.css
.offer {
  border: 2px solid #0f766e;
  border-radius: 10px;
  color: #0f766e;
}
  1. Token-এর ফাইল। রং আর মাপের সিদ্ধান্তগুলো — অর্থাৎ design token — এক জায়গায়। !default মানে “বাইরে থেকে কেউ অন্য মান না দিলে এটিই”। - বা _ দিয়ে শুরু হওয়া নাম private: অন্য ফাইল এটি ব্যবহার করতে পারবে না।
  2. যার যা লাগে, সে নিজেই @use করে। _mixins.scss-এর রং লাগবে, তাই সে @use "tokens" লিখেছে। এখন variable পাওয়া যায় namespace দিয়ে — tokens.$brand. নাম শুনেই বোঝা যায় মানটি কোন ফাইল থেকে এলো। একই module প্রজেক্টে যতবারই @use হোক, লোড হয় একবারই।
  3. @forward — এক প্রবেশপথ। _index.scss নিজে কিছু ব্যবহার করে না; সে শুধু দুই ফাইলের সদস্যদের পরের জনের হাতে তুলে দেয়। দোকানের সামনের কাউন্টারের মতো: ভেতরে কয়েকটি গুদাম, কিন্তু ক্রেতা একটি জানালা দিয়েই সব পান।
  4. মূল ফাইল। ফোল্ডারের নাম "abstracts" দিলে Sass নিজেই তার _index.scss পড়ে। as a দিয়ে ছোট namespace — এখন a.card, a.$brand. চাইলে @use "abstracts" as a with ($brand: #be123c); লিখে !default মানটি বদলে দেওয়া যায় — Bootstrap-এর মতো library এভাবেই নিজের রং বদলানোর সুযোগ দেয়।
  5. ফল — শুধু একটি CSS. চারটি .scss ফাইলের মধ্যে আউটপুট হলো একটিই, main.css. Partial-গুলো আলাদা ফাইল হয়নি, mixin আর variable-এর কোনো চিহ্নও নেই। ব্রাউজার পায় সাধারণ চার লাইন।

পুরোনো প্রজেক্টে কী করবেন? Sass দল একটি স্বয়ংক্রিয় রূপান্তর টুল দেয় — npx sass-migrator module --migrate-deps main.scss — যা @import-কে @use-এ বদলে প্রয়োজনীয় namespace বসিয়ে দেয়। বড় পরিবর্তনের আগে Git-এ commit করে নিন (পর্ব ৫৩), যাতে দরকার হলে আগের অবস্থায় ফেরা যায়। আর সাধারণ CSS-এর নিজের @import url(…) আলাদা জিনিস — সেটি deprecated নয়, তবে পারফরম্যান্সের কারণে (পর্ব ৪৯) বড় সাইটে এড়িয়ে চলাই ভালো।

৫ · Mixin, function আর @extend

Mixin — নামকরা declaration-এর গুচ্ছ

একই পাঁচ লাইন বারবার কপি না করে সেগুলোকে একটি নাম দিন — @mixin দিয়ে। তারপর যেখানে লাগবে, @include দিয়ে বসান। Mixin argument-ও নিতে পারে, তাই প্রতিবার একটু আলাদা ফল দেয়। আর @content দিয়ে সে বাইরে থেকে একটি পুরো ব্লক নিতে পারে — breakpoint-এর জন্য খুব জনপ্রিয় কৌশল:

ইনপুট · buttons.scss

@mixin button($bg, $fg: white) {
  background: $bg;
  color: $fg;
  border-radius: 8px;
}
@mixin from($bp) {
  @media (width >= $bp) { @content; }
}

.btn-buy { @include button(#0f766e); }
.btn-cancel {
  @include button(#e2e8f0, #0f172a);
}
.grid {
  @include from(40rem) {
    grid-template-columns: 1fr 1fr;
  }
}

আউটপুট

.btn-buy {
  background: #0f766e;
  color: white;
  border-radius: 8px;
}
.btn-cancel {
  background: #e2e8f0;
  color: #0f172a;
  border-radius: 8px;
}
@media (width >= 40rem) {
  .grid {
    grid-template-columns: 1fr 1fr;
  }
}

লক্ষ করুন, mixin আউটপুটে declaration কপি করে বসায় — border-radius: 8px দুবার এসেছে। শুনতে অপচয় মনে হলেও বাস্তবে সমস্যা কম, কারণ সার্ভার ফাইল gzip বা Brotli দিয়ে সংকুচিত করে পাঠায়, আর পুনরাবৃত্ত লেখা খুব ভালো সংকুচিত হয়। @media (width >= $bp) লাইনটিও খেয়াল করুন — media query-র শর্তে CSS custom property চলে না, কিন্তু Sass variable চলে, কারণ compile-এর পর সেখানে সাধারণ 40rem বসে যায়।

Function — হিসাব করে মান ফেরত

Mixin দেয় declaration; function দেয় একটি মান। Sass-এর নিজের function এখন module-এ ভাগ করা: sass:math, sass:color, sass:map, sass:string. আগে সরাসরি darken($c, 10%) বা map-get() লেখা হতো; Dart Sass 1.80 থেকে এসব global function deprecated, এখন লিখতে হয় color.adjust(), map.get(). ভাগ করার জন্য / চিহ্নও deprecated (CSS-এ / অন্য অর্থে আছে, যেমন grid-area: 1 / 3); তার বদলে math.div():

@use "sass:math";
@use "sass:color";

@function rem($px) {
  @return math.div($px, 16px) * 1rem;   // 24px → 1.5rem
}

.grid  { gap: rem(24px); }               // gap: 1.5rem;
.third { width: math.div(100%, 3); }     // width: 33.3333333333%;
.btn:hover {
  background: color.adjust(#0f766e, $lightness: -8%);
}

শেষ লাইনের ফল একটু চমকে দেওয়ার মতো। এখনকার Dart Sass রঙের হিসাব দশমিক পর্যন্ত নির্ভুল রাখে, তাই আউটপুটে hex কোড নয়, আসে rgb(4.077841663%, 32.0790210821%, 29.9041721952%) — সঠিক, কিন্তু পড়তে অস্বস্তিকর। আর রংটি চিরদিনের মতো স্থির; পরে --brand বদলালেও এই hover-রং বদলাবে না। অংশ ৭-এ দেখবেন, CSS-এর নিজের color-mix() এই কাজ ব্রাউজারেই, জীবন্তভাবে করে।

@extend — কেন এখন সাবধানে

@extend একটি selector-কে অন্য selector-এর সব নিয়মে জুড়ে দেয়। শুনতে সুন্দর — declaration কপি হয় না, selector-তালিকায় নাম যোগ হয়। কিন্তু “সব নিয়ম” মানে সত্যিই সব, ফাইলের অন্য কোণের নিয়মও। একটি পেমেন্ট পাতার উদাহরণ — SCSS-এ লেখা ছিল:

.error { color: #b91c1c; border-left: 4px solid; padding-left: .5rem; }
.sidebar .error { font-size: .8rem; }      // অন্য কেউ, অন্য সময়ে লিখেছিলেন
.payment-failed { @extend .error; font-weight: bold; }

Sass এর থেকে যে CSS বানায়, সেটিই নিচের এডিটরে:

/* Sass-এর তৈরি CSS (@extend থেকে) */
.error, .payment-failed {
  color: #b91c1c;
  border-left: 4px solid;
  padding-left: 0.5rem;
}
.sidebar .error, .sidebar .payment-failed {
  font-size: 0.8rem;
}
.payment-failed {
  font-weight: bold;
}

.page { font-family: sans-serif; display: grid; gap: 1rem; }
.sidebar { background: #f1f5f9; padding: .5rem; }

দুটি বার্তাই লাল আর মোটা, কিন্তু sidebar-এরটি হঠাৎ ছোট লেখায় — কেউ চায়নি! @extend চুপচাপ .sidebar .payment-failed নামের একটি নতুন selector বানিয়েছে, কারণ .error যেখানে যেখানে আছে, সেখানে সেখানেই সে নিজের নাম বসায়। চেষ্টা করুন: দ্বিতীয় নিয়মের , .sidebar .payment-failed অংশটি মুছুন — এটাই mixin ব্যবহার করলে পেতেন: শুধু যা চেয়েছেন। বড় প্রজেক্টে এমন জোড়া শত শত হয়ে selector explosion ঘটায়।

আরও দুটি সীমা আছে: @media-এর ভেতর থেকে বাইরের selector extend করা যায় না (Sass error দেয়: You may not @extend selectors across media queries), আর তৈরি selector কোথায় বসবে, তা আপনার লেখার ক্রমে নয়, মূল নিয়মের জায়গায় — ফলে cascade-এর ক্রম গুলিয়ে যায়। একান্তই ব্যবহার করলে placeholder selector (%message) extend করুন, যা নিজে কখনো আউটপুটে আসে না, তাই অন্য কোণের নিয়মে জড়ায় না। তবে বেশিরভাগ পেশাদার দল এখন সরল নিয়ম মানে: পুনর্ব্যবহারের জন্য mixin বা একটি সাধারণ class, @extend নয়।

৬ · Loop, শর্ত আর map — utility class কারখানা

এখানেই Sass এখনো সবচেয়ে শক্তিশালী — এমন কাজ, যা CSS-এর নিজের নেই। Control directive দিয়ে compile-এর সময় শর্ত আর loop চালানো যায়: @if/@else, @each (তালিকা বা map ঘুরে), @for (সংখ্যা গুনে)। আর map হলো নাম-মানের জোড়া — (ok: green, err: red). দুটো মিলিয়ে কয়েক লাইনে ডজনখানেক utility class তৈরি করা যায়; selector-এর নামের ভেতরে মান বসানো হয় interpolation #{…} দিয়ে।

নিচের ডায়াগ্রামে Sass একটি ফাইল পড়তে পড়তে কী মনে রাখে আর কী লেখে, লাইন ধরে ধরে দেখুন:

1@use "sass:math";2@use "sass:color";3$brand: #0f766e;4$space: 16px;5$status: (ok: #15803d, err: #b91c1c);6.btn {7  padding: math.div($space, 2) $space;8  background: $brand;9  &:hover {10    background: color.adjust($brand, $lightness: -8%);11  }12}13@each $name, $c in $status {14  .text-#{$name} { color: $c; }15}
Sass-এর খাতা (compile-এর সময়)
$brand
$space
$name
$c
  1. Module লোড। @use "sass:math" আর "sass:color" — Sass-এর নিজের দুটি module. এগুলো ফাইলের একেবারে শুরুতে থাকতে হয়, আর এখনো একটি অক্ষরও CSS লেখা হয়নি।
  2. Variable আর map খাতায় উঠল। $brand, $space আর $status map — সব Sass-এর মেমরিতে। আউটপুটে কিছুই যায়নি; variable নিজে কখনো CSS হয় না।
  3. নিয়ম শুরু, হিসাব হলো। .btn { আসতেই Sass আউটপুট লিখতে শুরু করে। math.div(16px, 2) = 8px, তাই লেখা হলো padding: 8px 16px; — ব্রাউজার কোনো ভাগ দেখবে না, শুধু ফল।
  4. Variable-এর জায়গায় মান। $brand-এর জায়গায় বসল #0f766e. নিয়মের ব্লক বন্ধ হলো।
  5. Nesting খুলে নতুন নিয়ম। &:hover হলো আলাদা নিয়ম .btn:hover. color.adjust() রংটিকে ৮% গাঢ় করল; আসল আউটপুট দশমিকের অনেক ঘর পর্যন্ত (এখানে ছোট করে দেখানো)। এই রং এখন স্থির — ব্রাউজারে বদলানো যাবে না।
  6. Loop — প্রথম ঘুর। @each map থেকে প্রথম জোড়া নিল: $name = ok, $c = #15803d. .text-#{$name} হলো .text-ok.
  7. Loop — দ্বিতীয় ঘুর। এবার err আর #b91c1c — তৈরি হলো .text-err. map-এ আরেকটি জোড়া যোগ করলে আরেকটি class নিজে থেকেই আসত। একটি map, এক লাইনের loop — যত খুশি class.

আরেকটু বড় কারখানা: তিনটি অবস্থার রং দিয়ে লেখা আর হালকা পটভূমির class, আর @for দিয়ে ফাঁকের মাপ:

@use "sass:color";
$colors: (ok: #15803d, warn: #b45309, err: #b91c1c);

@each $name, $c in $colors {
  .text-#{$name} { color: $c; }
  .bg-#{$name}   { background: color.change($c, $alpha: 0.12); }
}
@for $i from 1 through 3 {           // through = ৩ সহ; to = ৩ বাদে
  .mt-#{$i} { margin-top: $i * .5rem; }
}

এর তৈরি CSS (একটু গুছিয়ে, প্রতি নিয়ম এক লাইনে) নিচের এডিটরে, আর HTML-এ একটি অনলাইন দোকানের অর্ডারের অবস্থা:

/* Sass-এর তৈরি — ৯টি class, SCSS-এ লেগেছিল ৯ লাইন */
.text-ok { color: #15803d; }
.bg-ok { background: rgba(21, 128, 61, 0.12); }
.text-warn { color: #b45309; }
.bg-warn { background: rgba(180, 83, 9, 0.12); }
.text-err { color: #b91c1c; }
.bg-err { background: rgba(185, 28, 28, 0.12); }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; }

.orders { list-style: none; padding: 0; font-family: sans-serif; }
.orders li { padding: .5rem .75rem; border-radius: 6px; }

তিনটি সারি — সবুজ, কমলা-বাদামি আর লাল লেখা, প্রতিটির পেছনে একই রঙের খুব হালকা পটভূমি; দ্বিতীয় সারির ওপরে ছোট ফাঁক, তৃতীয়টির ওপরে বড় ফাঁক। চেষ্টা করুন: .mt-3-এর মান 3rem করুন — তৃতীয় সারি আরও নিচে সরে যায়। এবার নিজে একটি .text-info { color: #1d4ed8; } আর .bg-info লিখুন — Sass-এ এর জন্য map-এ শুধু info: #1d4ed8 যোগ করলেই হতো।

@if কোথায় কাজে লাগে? Mixin-এর ভেতরে, argument অনুযায়ী আলাদা CSS লিখতে: @mixin theme($mode) { @if $mode == dark { background: #0f172a; color: white; } @else { background: white; color: #0f172a; } }. তবে মনে রাখবেন, শর্তটি যাচাই হয় compile-এর সময় — ব্যবহারকারী পাতা চলার সময় থিম বদলালে Sass কিছুই জানবে না; সেটির জন্য পর্ব ৩৬-এর prefers-color-scheme আর custom property. সতর্কতা: loop খুব সহজে হাজার হাজার class বানাতে পারে, যার বেশিরভাগ হয়তো কোনো পাতায় ব্যবহারই হয় না — ফাইল ফুলে ওঠে। দরকারের বেশি বানাবেন না।

৭ · আধুনিক CSS কতটা জায়গা নিয়েছে — Sass আর কখন

২০১৫ সালের দিকে Sass ছাড়া বড় প্রজেক্ট কল্পনাই করা যেত না। তারপর CSS নিজেই একে একে Sass-এর জনপ্রিয় feature-গুলো এনেছে — ওয়েবের মানক তৈরিতে একে বলে “গরুর হাঁটা পথ পাকা করা” (paving the cowpaths): মানুষ যে পথে বারবার হাঁটে, সেটিকেই রাস্তা বানানো।

Sass-এএখন CSS-এBaseline (২০২৬)
$brand--brand + var() (পর্ব ৩৩)Widely available
nesting, &native nesting (পর্ব ৩৫)Newly → Widely available (২০২৬-এর মাঝামাঝি)
math.div(), *, +calc(), min(), max(), clamp()Widely available
math.round(), math.mod()round(), mod(), rem()Newly available (২০২৪)
color.adjust(), color.mix()color-mix()Widely available
রঙের একটি অংশ বদলানোrelative color: oklch(from var(--brand) calc(l - 0.1) c h)Newly available (২০২৪)
partial আর @use দিয়ে ভাগ@layer দিয়ে ক্রম গোছানো (ফাইল জোড়া নয়)Widely available
@mixin, @functionnative @function আর @mixin-এর কাজ চলছে; Chromium-ভিত্তিক ব্রাউজারে custom function আসতে শুরু করেছেএখনো Baseline নয়
@each, @forনেই—

একই অফার-কার্ড এবার কোনো build ছাড়া, শুধু আধুনিক CSS দিয়ে — variable, nesting, calc() আর color-mix() সবই ব্রাউজারে:

.offer {
  --brand: #0f766e;
  --space: 1rem;
  font-family: sans-serif;
  max-width: 22rem;
  padding: var(--space);
  border: 2px solid var(--brand);
  border-radius: 10px;

  .offer-btn {
    background: var(--brand);
    color: white;
    border: 0;
    padding: calc(var(--space) / 2) var(--space);
    border-radius: 10px;
    &:hover { background: color-mix(in oklch, var(--brand), black 25%); }
  }
}

কার্ডটি অংশ ২-এর মতোই; বোতামে মাউস নিলে সে গাঢ় teal হয়। এবার শুধু --brand-কে #be123c করুন — বর্ডার, বোতাম আর hover-এর গাঢ় রং তিনটিই নিজে থেকে লাল-ভিত্তিক হয়ে গেল। Sass-এর color.adjust()-এ hover-রং আগেই হিসাব হয়ে স্থির থাকত। চেষ্টা করুন: black 25%-কে white 30% করুন — hover-এ বোতাম হালকা হয়।

তাহলে Sass কখন এখনো ঠিক পছন্দ?

  • Loop আর map দিয়ে অনেক class বা token তৈরি: একটি design system-এর রং-সারণি থেকে শত শত utility — CSS-এ এর কোনো বিকল্প নেই।
  • Mixin দিয়ে জটিল, argument-ওয়ালা প্যাটার্ন: native mixin এখনো Baseline নয়।
  • Compile-এর সময়ের নিরাপত্তা: ভুল variable নাম, ভুল একক (16px + 2s) — Sass error দিয়ে build থামায়; ব্রাউজার নীরবে উপেক্ষা করে।
  • Media query-র শর্তে নামকরা breakpoint: @media (width >= $bp-md).
  • বড় পুরোনো প্রজেক্ট আর library: Bootstrap 5-এর মূল কোড Sass-এ লেখা; তার রং বা breakpoint বদলাতে Sass-ই সবচেয়ে পরিষ্কার পথ (পর্ব ৫১)।

আর ছোট সাইট, ল্যান্ডিং পাতা বা নতুন প্রজেক্টে? আধুনিক CSS-ই প্রায়ই যথেষ্ট — একটি টুল কম মানে শেখার, সেটআপের আর ভাঙার জিনিস কম।

৮ · PostCSS — plugin দিয়ে CSS বদলানোর যন্ত্র

PostCSS নিজে প্রায় কিছুই করে না — এটাই তার শক্তি। সে CSS ফাইলকে পড়ে একটি গাছের মতো কাঠামোয় ভাঙে, যাকে বলে AST: প্রতিটি নিয়ম একটি শাখা, তার ভেতরে selector আর প্রতিটি declaration আলাদা পাতা। তারপর আপনার বেছে নেওয়া plugin-গুলো একে একে সেই গাছ বদলায়, আর শেষে PostCSS গাছটিকে আবার লেখায় ফেরায়। ২০১৩ সালে Andrey Sitnik প্রথমে Autoprefixer বানান; তার ভেতরের CSS-পড়ার অংশটিই পরে আলাদা হয়ে হয় PostCSS.

সবচেয়ে প্রচলিত plugin:

  • Autoprefixer: আপনি লেখেন সাধারণ user-select: none; সে Can I use-এর তথ্য দেখে শুধু যেখানে দরকার সেখানে -webkit- জাতীয় vendor prefix যোগ করে, আর অপ্রয়োজনীয় পুরোনো prefix মুছে দেয়। হাতে prefix লেখার আর দরকার নেই।
  • Browserslist: কোন ব্রাউজার সমর্থন করবেন, তা এক লাইনে — যেমন defaults (মোটামুটি “০.৫%-এর বেশি ব্যবহার, প্রতিটির শেষ দুই সংস্করণ, Firefox ESR, আর বন্ধ হয়ে যায়নি এমন”)। সাম্প্রতিক সংস্করণে baseline widely available-এর মতো Baseline-ভিত্তিক query-ও আছে। Autoprefixer, postcss-preset-env, Lightning CSS — সবাই এই একই তালিকা পড়ে।
  • postcss-preset-env: অনেকগুলো plugin-এর প্যাকেট, যা নতুন CSS (যেমন nesting) আপনার লক্ষ্য-ব্রাউজার অনুযায়ী পুরোনো রূপে নামিয়ে আনে; ভেতরে Autoprefixer-ও থাকে, তাই দুটো একসাথে আলাদা করে দিতে হয় না।
  • cssnano: minification — ফাঁকা জায়গা, মন্তব্য, বাড়তি অক্ষর বাদ দিয়ে ফাইল ছোট করে, অর্থ না বদলে।

নিচের ডায়াগ্রামে একটি ছোট CSS তিনটি plugin পার হয়ে কীভাবে বদলায়, ধাপে ধাপে দেখুন (আউটপুটগুলো আসল টুল চালিয়ে নেওয়া, browserslist defaults):

  1. ইনপুটmain.cssলেখা
  2. ParseASTগাছ
  3. nestingpostcss-nestingসমতল
  4. prefixautoprefixerলেবেল
  5. ছোটcssnanoপ্যাকিং
main.cssআধুনিক CSS-এ লেখা
.hero {
  user-select: none;
  backdrop-filter: blur(4px);
  &:hover { opacity: .9; }
}
ASTPostCSS-এর চোখে ফাইলটি
Root
└─ Rule  .hero
   ├─ Decl  user-select : none
   ├─ Decl  backdrop-filter : blur(4px)
   └─ Rule  &:hover
      └─ Decl  opacity : .9
postcss-nesting-এর পরেভেতরের নিয়ম বাইরে এলো
.hero {
  user-select: none;
  backdrop-filter: blur(4px);
}
.hero:hover { opacity: .9; }
autoprefixer-এর পরেbrowserslist: defaults
.hero {
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  backdrop-filter: blur(4px);
}
.hero:hover { opacity: .9; }
main.min.csscssnano-র পরে, এক লাইনে
.hero{-webkit-user-select:none;-moz-user-select:none;user-select:none;backdrop-filter:blur(4px)}.hero:hover{opacity:.9}
  1. ইনপুট। হাতে লেখা বা Sass-এর তৈরি সাধারণ CSS — এখানে native nesting-সহ। কোনো prefix হাতে লেখা হয়নি।
  2. Parse — লেখা থেকে গাছ। PostCSS ফাইলটিকে নোডের গাছে ভাঙে: Root, তার ভেতরে Rule, তার ভেতরে Decl (declaration). এখন plugin-গুলো লেখা নয়, কাঠামো নিয়ে কাজ করবে — “এই Rule-টি সরাও”, “এই Decl-এর আগে আরেকটি বসাও”।
  3. Nesting সমতল। postcss-nesting ভেতরের &:hover Rule-কে বাইরে তুলে আনল .hero:hover হিসেবে। খুব পুরোনো ব্রাউজারও এখন বুঝবে। (আপনার browserslist-এর সব ব্রাউজার nesting বুঝলে এই ধাপ বাদ দিতে পারেন।)
  4. Prefix — শুধু যেখানে দরকার। user-select-এর আগে -webkit- আর -moz- বসল, কারণ লক্ষ্য-তালিকার কিছু ব্রাউজার এখনো এই নামেই চেনে। কিন্তু backdrop-filter-এ কিছু বসেনি — তালিকার সব ব্রাউজার এটি prefix ছাড়াই বোঝে। হাতে লিখলে এই সূক্ষ্ম পার্থক্য রাখা প্রায় অসম্ভব।
  5. ছোট করা। cssnano ফাঁকা জায়গা আর লাইন ভাঙা সরাল, শেষ ; বাদ দিল — অর্থ হুবহু এক, ফাইল ছোট। সাথে একটি source map রাখলে DevTools তবু মূল লাইন দেখাবে (অংশ ৯)।

আরও গভীরে: PostCSS-এর AST-এ নোড মাত্র কয়েক রকম — Root, AtRule (@media), Rule, Declaration, Comment — আর প্রতিটি নোড মনে রাখে সে মূল ফাইলের কোন লাইন-কলাম থেকে এসেছে। PostCSS 8 থেকে plugin-গুলো “visitor” হিসেবে লেখা হয় (যেমন Declaration(decl) { … }), ফলে সব plugin মিলে গাছটি মাত্র একবার ঘোরা হয়। Source map-এ এই অবস্থানগুলো Base64 VLQ নামের ঘন সংকেতে লেখা থাকে ("mappings":"AAEA;ECCE…"); দীর্ঘদিন অনানুষ্ঠানিক “version 3” প্রস্তাব হিসেবে চলার পর ২০২৪ সালে Ecma এটিকে ECMA-426 নামে আনুষ্ঠানিক মানদণ্ড করে।

Tailwind আর Lightning CSS — কে কার আত্মীয়

  • Tailwind CSS: সংস্করণ ৩ পর্যন্ত Tailwind ছিল মূলত একটি PostCSS plugin — আপনার HTML খুঁজে যে class ব্যবহার হয়েছে, শুধু সেগুলোর CSS বানাত। সংস্করণ ৪ (২০২৫) নিজের দ্রুত engine-এ চলে আর ভেতরে prefix ও syntax-রূপান্তরের জন্য Lightning CSS ব্যবহার করে; PostCSS দিয়ে চালানোর একটি plugin এখনো দেয়। বিস্তারিত পর্ব ৫১-এ।
  • Lightning CSS: Parcel bundler-এর নির্মাতা Devon Govett-এর বানানো, Rust ভাষায় লেখা টুল। একাই parse, browserslist অনুযায়ী prefix, nesting-এর মতো নতুন syntax নামানো আর minify করতে পারে, আর JavaScript-এ লেখা টুলের চেয়ে সাধারণত অনেক দ্রুত। অর্থাৎ Autoprefixer + preset-env + cssnano-র কাজ এক টুলে। তবে PostCSS-এর বিশাল plugin-জগতের সব কিছু এতে নেই — বিশেষ কোনো plugin লাগলে PostCSS-ই ভরসা। Vite-এর মতো build টুলে এটি বিকল্প হিসেবে বেছে নেওয়া যায়।

Prefix হাতে লিখবেন না, আর অন্ধভাবেও যোগ করবেন না। AI আর পুরোনো টিউটোরিয়াল প্রায়ই -webkit-border-radius, -moz-box-shadow, -webkit-transition দেয় — এসব বহু বছর ধরে অপ্রয়োজনীয়। প্রকৃত প্রয়োজন কোথায়, সেটি আপনার browserslist আর Can I use-এর তথ্য ঠিক করবে, অনুমান নয়। Feature নিজেই আছে কি না, সেটি যাচাইয়ের কাজ @supports-এর (পর্ব ৪৮) — prefix সেই প্রশ্নের উত্তর নয়।

৯ · সেটআপ — npm, script, VS Code আর source map

Sass আর PostCSS দুটিই npm দিয়ে আসে, তাই আগে Node.js ইনস্টল করুন (JavaScript কোর্সের npm আর Vite-এর পর্বে বিস্তারিত)। তারপর প্রজেক্ট ফোল্ডারে terminal খুলে:

npm init -y                                  # package.json তৈরি
npm install --save-dev sass                  # Dart Sass
npm install --save-dev postcss postcss-cli autoprefixer cssnano

এবার package.json-এর "scripts" অংশে কমান্ডগুলোর নাম দিন, যাতে প্রতিবার লম্বা কমান্ড মনে রাখতে না হয়:

{
  "scripts": {
    "dev": "sass src/main.scss dist/main.css --watch",
    "build:css": "sass src/main.scss dist/main.css --style=compressed",
    "build": "npm run build:css && postcss dist/main.css -o dist/main.min.css"
  },
  "browserslist": ["defaults"]
}
module.exports = {
  plugins: [require('autoprefixer'), require('cssnano')]
};
  • npm run dev — watch mode: Sass চালু থেকে নজর রাখে; .scss সেভ করলেই নতুন main.css. VS Code-এর Live Server-এর মতো live reload থাকলে পাতাও নিজে থেকে নতুন হয়।
  • npm run build — প্রকাশের আগে: compressed CSS, তারপর PostCSS-এর prefix আর minify. HTML-এ <link rel="stylesheet" href="dist/main.min.css">.
  • "browserslist" — একবার লিখলে সব টুল একই তালিকা মানে। npx browserslist চালালে দেখায় তালিকায় ঠিক কোন কোন ব্রাউজার পড়েছে।

FTP বা cPanel দিয়ে সাইট তোলেন? তাহলে সার্ভারে শুধু dist/-এর CSS ফাইল তুলুন; src/-এর .scss আর node_modules ফোল্ডার সার্ভারে লাগে না। Vite বা অন্য bundler ব্যবহার করলে কাজ আরও কম — sass প্যাকেজ ইনস্টল থাকলেই সে .scss ফাইল নিজে compile করে, আর postcss.config.js থাকলে নিজে থেকে PostCSS চালায়।

VS Code আর source map

VS Code নিজেই SCSS কোড রঙিন করে দেখায় আর কিছু ভুল ধরিয়ে দেয়। Marketplace-এ “Live Sass Compiler”-এর মতো extension আছে, যা সেভ করলেই compile করে — শুরুতে সুবিধাজনক, তবে এর ভেতরে Sass-এর নিজস্ব একটি সংস্করণ থাকে, যা আপনার প্রজেক্টের সংস্করণের সাথে না-ও মিলতে পারে। দলের কাজে npm script-ই ভালো: সবার কম্পিউটারে একই সংস্করণ, একই ফল।

Compile করা CSS-এ সমস্যা হলে DevTools দেখায় main.css-এর লাইন ১৪৭ — কিন্তু আপনি তো লিখেছেন _buttons.scss! সমাধান source map. ফাইলে লেখার সময় Dart Sass ডিফল্টভাবে main.css.map বানায় আর CSS-এর শেষে একটি মন্তব্য রাখে:

/*# sourceMappingURL=main.css.map */

DevTools এই মন্তব্য দেখে map ফাইলটি পড়ে, আর Styles প্যানেলে সরাসরি দেখায় _buttons.scss:12 (পর্ব ৪৭)। প্রকাশের সময় map সার্ভারে রাখবেন কি না, সেটি আপনার সিদ্ধান্ত — রাখলে যে কেউ আপনার মূল SCSS দেখতে পারবে; না চাইলে --no-source-map.

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

  • Preprocessor আর postprocessor-এর পার্থক্য? — Preprocessor নিজের ভাষা (SCSS) থেকে CSS বানায়; postprocessor তৈরি CSS নিয়ে তাকে বদলায় (prefix, minify).
  • Sass variable আর CSS custom property-র মূল পার্থক্য? — Sass-এরটি compile-এর সময় মুছে যায়; custom property ব্রাউজারে থাকে, cascade মানে আর চলার সময় বদলায়।
  • @use কেন @import-এর চেয়ে ভালো? — namespace দেয়, প্রতিটি module একবারই লোড হয়, private সদস্য রাখা যায়; Sass-এর @import এখন deprecated.
  • Mixin আর @extend-এর পার্থক্য? — Mixin declaration কপি করে; @extend selector-তালিকায় নাম জোড়ে, যা অপ্রত্যাশিত selector আর ক্রমের সমস্যা তৈরি করতে পারে।
  • Autoprefixer কীভাবে বোঝে কোন prefix লাগবে? — browserslist-এর লক্ষ্য-তালিকা আর Can I use-এর ডেটা মিলিয়ে।

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

AI-কে “একটি SCSS ফাইল লিখে দাও” বললে সে দ্রুত সুন্দর কোড দেয়। কিন্তু তার শেখার ডেটার বড় অংশ ২০১০-এর দশকের — যখন @import, darken() আর হাতে লেখা prefix-ই ছিল নিয়ম। ফলে কয়েকটি ভুল বারবার আসে:

AI যা দেয়সমস্যাআধুনিক বিকল্প
@import "variables";deprecated, global নাম; সতর্কবার্তা আসে@use "variables"; (বা @forward)
darken($c, 10%), map-get()global function, deprecatedcolor.adjust()/color.scale(), map.get(), বা CSS-এর color-mix()
$gap / 2/ দিয়ে ভাগ deprecatedmath.div($gap, 2) বা calc()
npm install node-sassLibSass-ভিত্তিক, বহু আগে deprecatednpm install -D sass
সাধারণ .css ফাইলে $primary বা @includeব্রাউজার বোঝে না, নীরবে বাদফাইল .scss করে compile, বা custom property
হাতে -webkit-, -moz- prefixবেশিরভাগই অপ্রয়োজনীয়, কিছু ভুলওAutoprefixer + browserslist
চার-পাঁচ স্তর nesting, ঢালাও @extendভারী selector, selector explosionদুই-তিন স্তর, mixin বা class

AI-কে প্রশ্ন করার সময় সংস্করণ আর নিয়ম শুরুতেই বেঁধে দিন, আর তৈরি CSS-ও দেখতে চান — তাহলে যাচাই সহজ হয়:

আমি Dart Sass-এর সর্বশেষ সংস্করণ (npm-এর sass প্যাকেজ) ব্যবহার করি। একটি অনলাইন দোকানের বোতামের জন্য SCSS লিখে দাও: রং একটি map-এ থাকবে (primary, danger, muted), আর @each দিয়ে .btn-primary, .btn-danger, .btn-muted তৈরি হবে। শর্ত: ১. শুধু @use আর module function (color.*, map.*, math.*) — @import, darken(), map-get() বা / দিয়ে ভাগ নয়। ২. কোনো vendor prefix হাতে লিখবে না; nesting দুই স্তরের বেশি নয়; @extend নয়। ৩. পাশে compile করা CSS-ও দেখাও। ৪. শেষে বলো, এর কোন অংশ আধুনিক CSS (custom property, color-mix()) দিয়ে build ছাড়াই করা যেত।

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

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

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

প্র ০১Sass তিনবার নতুন করে লেখা হলো — Ruby, তারপর C++, তারপর Dart. কেন? আর LibSass দ্রুত হয়েও হারল কেন?

প্রথম Sass লেখা হয়েছিল Ruby-তে, কারণ ২০০৬ সালে Ruby on Rails-এর জগৎ থেকেই এর জন্ম। সমস্যা দেখা দিল দুটি: Ruby-তে বড় ফাইল compile করা ধীর ছিল, আর যে ডেভেলপাররা Ruby ব্যবহার করেন না (PHP, Python বা Node.js-এর জগৎ), তাঁদের শুধু CSS বানাতে Ruby ইনস্টল করতে হতো।

সমাধান হিসেবে এলো LibSass — C++-এ লেখা, অনেক দ্রুত, আর যেকোনো ভাষা থেকে ব্যবহারযোগ্য। Node.js-এ এর মোড়ক ছিল node-sass, যা বহু বছর ধরে সবচেয়ে জনপ্রিয় ছিল। কিন্তু দুটি আলাদা সংস্করণ (Ruby আর C++) একই ভাষার নিয়ম মানবে, এটা নিশ্চিত করা কঠিন — নতুন feature এলে LibSass পিছিয়ে পড়ত। আর node-sass ছিল native C++ module: Node-এর প্রতিটি নতুন সংস্করণে আবার build করতে হতো, Windows-এ ইনস্টলের সময় Python আর C++ build টুল লাগত। “node-gyp error” লেখা সেই লাল terminal অনেক নতুন ডেভেলপারের প্রথম হতাশার অভিজ্ঞতা।

Dart Sass এই দুই সমস্যারই উত্তর। Dart থেকে একে সরাসরি দ্রুত native প্রোগ্রাম বানানো যায়, আবার JavaScript-এও রূপান্তর করা যায় — npm-এর sass প্যাকেজ বিশুদ্ধ JavaScript, ইনস্টলে কোনো C++ টুল লাগে না। একটিমাত্র মূল সংস্করণ থাকায় module system-এর মতো বড় পরিবর্তন সব জায়গায় একসাথে আসে। গতির জন্য আছে sass-embedded প্যাকেজ, যা ভেতরে native Dart Sass চালায়। ২০১৯ সালে Ruby Sass-এর মেয়াদ শেষ হলো, ২০২০ সালে LibSass deprecated.

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

প্র ০২CSS-এ এখন variable, nesting, color-mix() সবই আছে — তাহলে কি Sass মরে যাচ্ছে?

“মরে যাচ্ছে” নয়, বরং তার ভূমিকা বদলাচ্ছে। ইতিহাসের দিকে তাকালে একটি ধরন দেখা যায়: preprocessor কোনো feature জনপ্রিয় করে তোলে, লাখো ডেভেলপার বছরের পর বছর ব্যবহার করে প্রমাণ করেন যে এটি দরকারি, তারপর W3C-র CSS Working Group সেটিকে মানক করে — কখনো ভিন্ন, আরও শক্তিশালী রূপে। Custom property Sass variable-এর মতো দেখতে হলেও cascade মানে আর চলার সময় বদলায়; native nesting :is()-এর নিয়মে চলে। অর্থাৎ Sass ছিল পরীক্ষাগার, CSS হলো কারখানা।

বাস্তবে Sass এখনো বিপুল পরিমাণে ব্যবহৃত হয়। Bootstrap 5-এর মূল কোড Sass-এ, অসংখ্য কোম্পানির design system-এর token আর utility তৈরি হয় Sass-এর map আর loop দিয়ে। ডেভেলপারদের নিয়মিত জরিপেও (যেমন State of CSS) Sass এখনো সবচেয়ে পরিচিত CSS টুলগুলোর একটি। তবে নতুন, ছোট প্রজেক্টে মানুষ ক্রমেই সরাসরি আধুনিক CSS লিখছেন, কারণ যে তিনটি কারণে Sass লাগত — variable, nesting, হিসাব — তিনটিই এখন অনেকটা ব্রাউজারেই হয়।

সামনে কী? CSS Working Group native @function আর @mixin নিয়ে কাজ করছে, আর Chromium-ভিত্তিক ব্রাউজারে custom function আসতে শুরু করেছে; সব ব্রাউজারে পৌঁছালে Sass-এর আরেকটি বড় কারণ কমবে। কিন্তু loop দিয়ে compile-এর সময় শত শত class বানানো, বা ভুল নাম আর ভুল এককে build থামিয়ে দেওয়ার মতো “compile-time” কাজ ব্রাউজারের দায়িত্বের বাইরে — সেখানে কোনো না কোনো build টুল থাকবেই।

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

প্র ০৩Vendor prefix এলো কেন, আর ব্রাউজারগুলো কেন এখন prefix-এর বদলে “flag” ব্যবহার করে?

২০০০-এর দশকের শেষে ব্রাউজার নির্মাতারা স্পেসিফিকেশন চূড়ান্ত হওয়ার আগেই নতুন CSS পরীক্ষা করতে চাইতেন। সমাধান ছিল prefix: Safari আর Chrome-এ -webkit-border-radius, Firefox-এ -moz-, Internet Explorer-এ -ms-, Opera-য় -o-. ধারণা ছিল — পরীক্ষামূলক feature আলাদা নামে থাকুক, যাতে চূড়ান্ত নিয়ম বদলালে পুরোনো সাইট না ভাঙে।

বাস্তবে উল্টোটা হলো। ডেভেলপাররা পরীক্ষামূলক feature সরাসরি আসল সাইটে ব্যবহার করতে শুরু করলেন, অনেকে শুধু -webkit- লিখতেন, কারণ iPhone-এর Safari-ই ছিল মোবাইলের রাজা। ফলে অন্য ব্রাউজারে সাইট ভাঙতে লাগল, আর একসময় Firefox-সহ অন্যরাও কিছু -webkit- নাম বুঝতে বাধ্য হলো — এর জন্য আলাদা একটি “Compatibility” স্পেসিফিকেশনই লিখতে হয়েছে। হাতে prefix লেখাও ছিল ক্লান্তিকর আর ভুলে ভরা: পাঁচ রকম নাম, প্রতিটির আলাদা পুরোনো syntax (gradient-এর পুরোনো -webkit-gradient()-এর কথা অনেকেরই মনে আছে)।

২০১৩ সালে Autoprefixer এই সমস্যার পরিষ্কার সমাধান দিল: আপনি শুধু মানক নাম লিখুন, কোন ব্রাউজারে কী লাগে, তা Can I use-এর ডেটা দেখে টুল ঠিক করবে। প্রায় একই সময়ে ব্রাউজার নির্মাতারা নীতি বদলালেন — ২০১০-এর দশকের মাঝামাঝি Chrome, Firefox আর Safari একে একে জানায় যে নতুন পরীক্ষামূলক feature আর prefix দিয়ে নয়, ব্রাউজারের সেটিংয়ের “flag”-এর পেছনে বা origin trial-এ আসবে। সাধারণ ব্যবহারকারীর কাছে feature পৌঁছায় শুধু তখনই, যখন সেটি স্থির। আজকের Baseline ধারণাও সেই নীতিরই ফল।

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

প্র ০৪Mixin declaration কপি করে, @extend করে না — তবু বিশেষজ্ঞরা mixin-কেই নিরাপদ বলেন কেন? ফাইলের আকার কি বড় হয় না?

প্রথম দেখায় @extend-কে বেশি কার্যকর মনে হয়: দশটি class একই পাঁচটি declaration চাইলে mixin সেগুলো দশবার লেখে, আর @extend একবার লিখে selector-তালিকায় দশটি নাম জোড়ে। কিন্তু তিনটি বাস্তব কারণে হিসাবটি উল্টে যায়।

১. সংকোচন: সার্ভার CSS পাঠায় gzip বা Brotli দিয়ে সংকুচিত করে। এই অ্যালগরিদমগুলো পুনরাবৃত্ত লেখা খুঁজে ছোট সংকেতে বদলায় — একই পাঁচ লাইন দশবার থাকলে সংকুচিত আকার প্রায় একবারের কাছাকাছিই থাকে। অর্থাৎ mixin-এর “অপচয়” ডাউনলোডের সময় প্রায় মুছে যায়।

২. অপ্রত্যাশিত জোড়া: @extend মূল selector যেখানে যেখানে আছে — অন্য ফাইলে, অন্য প্রসঙ্গে, অন্য কারও লেখা নিয়মে — সব জায়গায় নিজের নাম বসায়। বড় প্রজেক্টে এতে কয়েক লাইনের SCSS থেকে বিশাল, জটিল selector-তালিকা তৈরি হয়েছে — যা কখনো কখনো মূল CSS-এর চেয়েও বড়, আর debug করা দুঃস্বপ্ন। তৈরি selector বসে মূল নিয়মের জায়গায়, ফলে cascade-এর ক্রমও আপনার লেখার ক্রম মানে না।

৩. সীমা: @media-এর ভেতর থেকে বাইরের selector extend করা যায় না, তাই responsive কাজে এটি প্রায়ই অচল।

এজন্যই অভিজ্ঞ দলগুলোর নিয়ম: পুনর্ব্যবহার দরকার হলে mixin, বা HTML-এ একটি সাধারণ class যোগ করুন (class="alert alert-error"). utility-first পদ্ধতি (পর্ব ৪৬ ও ৫১) এই চিন্তাকেই আরও এগিয়ে নিয়েছে।

মূল উপলব্ধি: কোডের সৌন্দর্য নয়, ফলের পূর্বানুমেয়তা আসল। যে টুলের ফল চোখ বুজে আন্দাজ করা যায়, বড় দলে সেটিই নিরাপদ — কয়েক কিলোবাইটের চেয়ে কয়েক ঘণ্টার debug অনেক দামি।

প্র ০৫Build step-এর দাম কী? Node.js আর npm-এর শত শত প্যাকেজ ছাড়া কি পেশাদার সাইট বানানো যায় না?

Build step মানে আপনার সাইট এখন শুধু আপনার লেখা ফাইল নয় — তার পেছনে একটি টুলের শিকল। npm install postcss-cli autoprefixer cssnano চালালে সাধারণত শত শত ছোট প্যাকেজ node_modules-এ নামে, যাদের বেশিরভাগের নামও আপনি জানেন না। এর তিনটি দাম আছে।

রক্ষণাবেক্ষণ: দুই বছর পর পুরোনো প্রজেক্ট খুলে npm install চালালে প্রায়ই কিছু না কিছু ভাঙে — Node-এর নতুন সংস্করণ, বাতিল প্যাকেজ (node-sass-এর কথা মনে করুন)।

নিরাপত্তা: ২০১৮ সালে জনপ্রিয় npm প্যাকেজ event-stream-এর রক্ষণাবেক্ষণের দায়িত্ব এক অচেনা ব্যক্তির হাতে যায়, যিনি একটি নির্ভরতার ভেতরে লুকানো ক্ষতিকর কোড ঢোকান — লক্ষ্য ছিল একটি নির্দিষ্ট cryptocurrency wallet অ্যাপ। Build টুলও এই supply chain-এর অংশ; তাই package-lock.json রাখা, অপ্রয়োজনীয় প্যাকেজ না রাখা আর npm audit চালানো জরুরি।

গতি: বড় প্রজেক্টে JavaScript-এ লেখা টুল ধীর হতে পারে — এজন্যই Lightning CSS-এর মতো Rust-ভিত্তিক টুল জনপ্রিয় হচ্ছে।

উল্টো দিকে একটি “no-build” আন্দোলনও আছে। Ruby on Rails 7 (২০২১) ডিফল্টভাবে import map দিয়ে build ছাড়াই JavaScript আনার পথ দেখায়, আর আধুনিক CSS-এর nesting, custom property, @layer থাকায় অনেক সাইট সাধারণ .css ফাইলেই চলছে। আপনার যদি কয়েক পাতার একটি static সাইট থাকে, যা FTP দিয়ে তোলেন, তাহলে build ছাড়া সাধারণ CSS-ই সবচেয়ে টেকসই — দশ বছর পরেও কোনো টুল ছাড়াই খোলা আর বদলানো যাবে।

মূল উপলব্ধি: প্রতিটি টুল একটি ঋণ — আজ সময় বাঁচায়, কাল রক্ষণাবেক্ষণ চায়। পেশাদারিত্ব মানে সবচেয়ে বেশি টুল নয়, প্রজেক্টের আকার আর আয়ু অনুযায়ী ঠিক যতটুকু দরকার, ততটুকু।

অনুশীলন

প্রথম দুটি কাগজে-কলমে; শেষ দুটির জন্য Node.js ইনস্টল থাকলে ভালো, না থাকলে লাইভ এডিটরেই করা যায়।

  1. নিজে compiler হন: নিচের SCSS থেকে Sass কোন CSS বানাবে, হাতে লিখুন — $c: #1d4ed8; $sizes: (sm: 12px, lg: 20px); .tag { color: $c; &:hover { color: black; } } @each $k, $v in $sizes { .tag-#{$k} { font-size: $v; } }
    .tag {
      color: #1d4ed8;
    }
    .tag:hover {
      color: black;
    }
    .tag-sm {
      font-size: 12px;
    }
    .tag-lg {
      font-size: 20px;
    }

    চারটি নিয়ম: variable-এর লাইন দুটি উধাও, nesting খুলে .tag:hover আলাদা নিয়ম, আর map-এর দুই জোড়া থেকে loop দুটি class বানিয়েছে। Node.js থাকলে npx sass দিয়ে চালিয়ে মিলিয়ে নিন।

  2. AI-এর পুরোনো কোড আধুনিক করুন: @import "colors"; .btn { padding: $gap/2; &:hover { background: darken($brand, 10%); } } — তিনটি deprecated জিনিস খুঁজে আধুনিক SCSS লিখুন।

    সমস্যা: (১) @import (২) / দিয়ে ভাগ (৩) global darken(). আধুনিক রূপ:

    @use "sass:math";
    @use "sass:color";
    @use "colors";
    
    .btn {
      padding: math.div(colors.$gap, 2);
      &:hover {
        background: color.adjust(colors.$brand, $lightness: -10%);
      }
    }

    খেয়াল করুন, @use-এর পর variable-এর আগে namespace (colors.) লাগে। Sass দলের sass-migrator টুল এই রূপান্তর স্বয়ংক্রিয়ভাবেও করতে পারে।

  3. Browserslist ঘুরে দেখুন: Node.js থাকলে যেকোনো ফোল্ডারে চালান npx browserslist "defaults", তারপর npx browserslist "last 1 version" আর npx browserslist "baseline widely available". তালিকাগুলো কত বড়? কোনটিতে Opera Mini আছে?

    defaults-এ থাকে Chrome, Edge, Firefox, Safari-র কয়েকটি করে সংস্করণ, সাথে Samsung Internet, Android-এর ব্রাউজার, KaiOS আর Opera Mini-ও। শেষ দুটির ব্যবহারের হার ০.৫%-এর কম, তবু তারা আসে defaults-এর “প্রতিটির শেষ দুই সংস্করণ” শর্তের কারণে। last 1 version প্রতিটি ব্রাউজারের শুধু সর্বশেষটি দেয়, তাই অনেক ছোট। Baseline-ভিত্তিক query-র তালিকা সাধারণত সবচেয়ে লম্বা — প্রধান ব্রাউজারগুলোর কয়েক বছরের সব সংস্করণ, যারা Baseline Widely available feature সমর্থন করে; Opera Mini সেখানে নেই। শিক্ষা: আপনার লক্ষ্য-তালিকা বদলালে Autoprefixer-এর আউটপুটও বদলায় — বাংলাদেশের দর্শকদের মধ্যে কম দামের Android ফোন আর পুরোনো ব্রাউজার বেশি হলে তালিকা সেই অনুযায়ী ঠিক করুন (Analytics-এর আসল ডেটা দেখে)।

  4. Sass থেকে native CSS: এই SCSS-কে build ছাড়া চলা সাধারণ CSS-এ লিখুন, যাতে hover-রং মূল রং থেকে নিজে হিসাব হয় — $brand: #7c3aed; .chip { background: $brand; color: white; &:hover { background: color.adjust($brand, $lightness: -10%); } }

    একটি সম্ভাব্য উত্তর — নিচের এডিটরে --brand বদলে দেখুন, hover-রংও সাথে বদলায়:

    .chip {
      --brand: #7c3aed;
      background: var(--brand);
      color: white;
      border: 0;
      padding: .4rem .9rem;
      border-radius: 99px;
      font: 16px sans-serif;
    
      &:hover {
        background: color-mix(in oklch, var(--brand), black 20%);
      }
    }

    দুটি বেগুনি “চিপ” বোতাম; মাউস নিলে গাঢ় হয়। color-mix() Baseline Widely available, nesting-ও এখন Widely available — তাই কোনো build লাগে না। খুব পুরোনো ব্রাউজারের কথা ভাবলে &:hover-কে বাইরে .chip:hover লিখুন, আর color-mix()-এর আগের লাইনে একটি সাধারণ fallback রং দিন। চেষ্টা করুন: --brand-কে #0f766e করুন — বোতাম আর hover-রং দুটিই teal-ভিত্তিক হয়।

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

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

ক্রমএকটি SCSS ফাইল থেকে ব্রাউজার পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ✍️ .scss ফাইলে variable, mixin আর loop দিয়ে লেখা
  2. ⚙️ Sass compile করে সাধারণ .css বানায়
  3. 🏷️ PostCSS-এর Autoprefixer browserslist দেখে prefix যোগ করে
  4. 📦 cssnano ফাইল ছোট করে, সাথে source map
  5. 🌐 ব্রাউজার <link> দিয়ে চূড়ান্ত CSS নামায়

কোনটি ব্যবহারকারী পাতা চলাকালীন :hover বা media query দিয়ে বদলানো যায়?

  • Sass variable $brand
  • CSS custom property --brand
  • Sass map $colors
  • Sass mixin @mixin card

২০২৬ সালে নতুন প্রজেক্টে Sass ইনস্টলের সঠিক উপায় কোনটি?

  • npm install node-sass
  • gem install sass (Ruby)
  • npm install --save-dev sass
  • HTML-এ একটি <script> দিয়ে Sass লোড করা

একটি Sass ফাইলে @use "tokens"; লেখা আছে। _tokens.scss-এর $brand সেখানে কীভাবে লিখতে হবে?

  • tokens.$brand
  • শুধু $brand
  • $tokens.brand
  • var(--tokens-brand)

Autoprefixer কোন তথ্যের ভিত্তিতে ঠিক করে কোন property-তে -webkit- লাগবে?

  • ব্যবহারকারীর ব্রাউজার পাতা খোলার সময় যা জানায়
  • সব property-তে সব prefix যোগ করে, নিরাপদ থাকার জন্য
  • CSS-এর @supports নিয়ম
  • browserslist-এর লক্ষ্য-তালিকা আর Can I use-এর ডেটা

Sass-এ @extend-এর বদলে mixin বা সাধারণ class ব্যবহারের পরামর্শ দেওয়া হয় কেন?

  • Dart Sass থেকে @extend সরিয়ে ফেলা হয়েছে
  • @extend অপ্রত্যাশিত selector তৈরি করে আর media query পেরিয়ে কাজ করে না
  • @extend declaration কপি করে বলে ফাইল অনেক বড় হয়
  • ব্রাউজার @extend বোঝে না, কিন্তু mixin বোঝে

Sass প্রজেক্টে _buttons.scss ফাইলের নামের শুরুতে _ থাকার অর্থ কী?

  • ফাইলের সব variable private
  • ফাইলটি সরাসরি ব্রাউজারে লোড হবে
  • এটি partial — নিজে আলাদা CSS ফাইল হবে না, অন্য ফাইল টেনে নেবে
  • ফাইলটি deprecated

শব্দকোষ

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

মনে রাখুন

  1. Preprocessor (Sass) নিজের ভাষা থেকে CSS বানায়, postprocessor (PostCSS) তৈরি CSS বদলায় — দুটিই build-এর সময়, ব্রাউজার শুধু ফল দেখে।
  2. Sass variable compile-এ মুছে যায়; custom property জীবন্ত। Sass nesting-এ &__ নাম জোড়ে, native nesting জোড়ে না, আর specificity-ও আলাদা।
  3. @use/@forward দিয়ে ফাইল ভাগ, namespace দিয়ে ব্যবহার; @import, darken(), / ভাগ deprecated — চাই color.adjust(), math.div().
  4. Mixin নিরাপদ পুনর্ব্যবহার, @extend সাবধানে; Sass-এর আসল জোর এখন loop আর map-এ।
  5. PostCSS: Autoprefixer + browserslist, postcss-preset-env, cssnano; Lightning CSS একই কাজের দ্রুত বিকল্প। সেটআপ: npm script, --watch, আর DevTools-এর জন্য source map.

পরের পর্বে: আজ CSS বানানোর টুল দেখলেন। এবার তৈরি CSS-এর বড় সংগ্রহ — Bootstrap আর Tailwind CSS. কোনটির দর্শন কী, Sass দিয়ে Bootstrap-এর রং কীভাবে বদলায়, Tailwind কীভাবে শুধু ব্যবহৃত class রাখে, আর কখন কোনোটিই নয়।

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

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