قراردادهای نام‌گذاری و BEM؛ وقتی اسم کلاس‌ها هم باید حرف بزنن

تا اینجا تو این دوره، بیشتر تمرکزمون رو خود ویژگی‌های CSS و رفتارشون بوده. اما یه پروژه‌ی واقعی، خصوصاً وقتی چند نفر روش کار می‌کنن یا چند ماه بعد بهش برمی‌گردید، به یه چیز دیگه هم نیاز داره: یه سیستم قابل‌پیش‌بینی برای اسم‌گذاری کلاس‌ها. این فصل به معماری و سازماندهی CSS تو مقیاس بزرگ می‌پردازه، و شروعش با یکی از معروف‌ترین قراردادهای نام‌گذاری، یعنی BEM، هست.

مشکلی که نام‌گذاری بد ایجاد می‌کنه

فرض کنید تو یه پروژه‌ی بزرگ، این کلاس‌ها رو می‌بینید: .title، .header، .item. مشکل اینجاست: این اسم‌ها هیچ زمینه‌ای ندارن. آیا .title مال عنوان یه کارته یا عنوان کل صفحه؟ اگه یه کلاس .title دیگه تو یه بخش دیگه‌ی سایت هم تعریف بشه، چه اتفاقی می‌افته؟ این دقیقاً همون مشکلیه که باعث می‌شه توسعه‌دهنده‌ها از ترس شکستن جاهای دیگه، اسم‌های جدید و تکراری بسازن به‌جای استفاده‌ی دوباره از چیزی که هست؛ و کم‌کم فایل CSS پر می‌شه از کلاس‌های مبهم و نامرتبط.

BEM چیه؟

BEM مخفف سه کلمه‌ست: Block، Element، Modifier. ایده‌ی اصلیش اینه که هر کلاس، از روی اسمش، دقیقاً بگه چه نقشی داره و به کدوم بخش تعلق داره، بدون این‌که نیاز باشه به CSS یا HTML اطرافش نگاه کنید.

بخشمعنیسینتکس
Blockیه کامپوننت مستقل و قابل‌استفاده‌ی دوباره.card
Elementیه بخش داخلی از block که به‌تنهایی معنا نداره.card__title
Modifierیه حالت یا نسخه‌ی متفاوت از block یا element.card--featured
<div class="card card--featured">
  <h3 class="card__title">عنوان کارت</h3>
  <p class="card__description">توضیحات</p>
  <button class="card__button card__button--disabled">خرید</button>
</div>
.card {
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  padding: 16px;
}

.card--featured {
  border-color: gold;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.card__title {
  font-size: 1.2rem;
  font-weight: bold;
}

.card__button--disabled {
  opacity: 0.5;
  pointer-events: none;
}

با نگاه به این کلاس‌ها، بدون دیدن HTML یا هیچ context دیگه‌ای، دقیقاً می‌فهمید .card__title عنوان داخل یه کارته، نه هر عنوان دیگه‌ای تو صفحه.

چرا دو زیرخط (__) و دو خط تیره (--)؟

این نمادها عمداً از یه خط تیره‌ی معمولی (که تو اسم خود block هم ممکنه به کار بره، مثل search-form) متمایز شدن تا مرورگر و خواننده بتونن سه بخش رو از هم تشخیص بدن:

.search-form { }              /* Block (با خط تیره‌ی تکی داخل اسمش) */
.search-form__input { }       /* Element */
.search-form__input--error { } /* Element با Modifier */

اگه به‌جای __ و -- فقط از یه خط تیره استفاده می‌کردیم، تشخیص این‌که search-form-input یعنی «element به اسم input داخل block به اسم search-form» یا «block ای به اسم search-form-input»، از روی خود اسم غیرممکن می‌شد.

قانون مهم: nesting element ها تو دل هم ممنوعه

یه اشتباه رایج اینه که element های تو در تو بسازید:

/* اشتباه: element داخل element */
.card__header__title { }

/* درست: همیشه مستقیم به block وصل می‌شه */
.card__title { }

حتی اگه از نظر ساختار HTML، title داخل یه header باشه که اون هم داخل card است، تو BEM همیشه element مستقیم به block وصل می‌شه، نه به element دیگه. این قانون به‌عمد ساده نگه داشته شده تا specificity و پیچیدگی selector ها پایین بمونه.

Modifier روی خود element هم قابل‌استفاده‌ست

Modifier محدود به خود block نیست؛ می‌تونید یه element رو هم modify کنید، دقیقاً همون‌طور که تو مثال .card__button--disabled بالا دیدیم. الگوی کلی همیشه اینه: block-name__element-name--modifier-name.

چرا BEM از nesting خود CSS (که تو فصل ۲۱ دیدیم) پرهیز می‌کنه؟

تو فصل ۲۱ با CSS nesting آشنا شدیم و دیدیم چقدر می‌تونه کد رو کوتاه‌تر کنه. اما پروژه‌های مبتنی بر BEM معمولاً عمداً از nesting عمیق پرهیز می‌کنن، حتی وقتی از نظر فنی امکانش هست:

/* از نظر فنی درسته، ولی خلاف روح BEM */
.card {
  .card__title {
    font-size: 1.2rem;
  }
}

/* روش استاندارد BEM: هر کلاس مستقل و flat */
.card { }
.card__title {
  font-size: 1.2rem;
}

دلیلش اینه که BEM از اول طراحی شده تا هر کلاس، مستقل از ساختار HTML اطرافش قابل‌فهم باشه؛ اگه selector ها رو تو در تو کنید، دوباره به همون وابستگی به ساختار که BEM می‌خواد ازش دور بشه برمی‌گردید (چون یه .card__title که تو یه selector تو در تو تعریف شده، دیگه مستقل نیست و اگه جای HTML عوض بشه ممکنه کار نکنه).

مزیت اصلی: specificity همیشه یکسان و پیش‌بینی‌پذیره

چون تو BEM تقریباً همیشه فقط از یه کلاس تنها استفاده می‌کنید (نه ترکیب تو در توی چند selector)، specificity همه‌ی قوانین تقریباً برابره. این یعنی مشکل کلاسیک «چرا این استایل override نمی‌شه» که تو فصل‌های اول دوره باهاش آشنا شدیم، عملاً کمتر پیش میاد؛ چون هیچ قانونی به‌خاطر nesting یا id، specificity غیرمنتظره‌ای نداره.

نکته مهم: BEM یه استاندارد رسمی CSS نیست؛ یه قراردادِ نام‌گذاریه که خود توسعه‌دهنده‌ها و تیم‌ها انتخاب می‌کنن رعایتش کنن. مرورگر هیچ فرقی بین .card__title و یه اسم دیگه نمی‌ذاره؛ ارزش BEM کاملاً برای خواننده‌ی انسانی و قابلیت نگهداری پروژه‌ست، نه چیزی که مرورگر بهش نیاز داشته باشه.

اشتباه رایج: ساختن block های خیلی بزرگ و همه‌کاره

یه اشتباه رایج اینه که کل یه صفحه یا یه بخش بزرگ رو یه block واحد در نظر بگیرید (مثل .homepage) و همه‌چیز داخلش رو element حساب کنید. این کار BEM رو از فایده می‌ندازه، چون block ها باید کوچیک و قابل‌استفاده‌ی دوباره باشن (مثل .card، .button، .nav)، نه بزرگ و یک‌بارمصرف. قاعده‌ی کلی: اگه یه بخش از UI رو می‌شه به‌تنهایی، مستقل از بقیه‌ی صفحه، تصور کرد (مثل یه کارت محصول)، احتمالاً کاندید خوبی برای یه block جداست.

جمع‌بندی

BEM با تقسیم هر کلاس به Block، Element و Modifier، اسم کلاس‌ها رو خودگویا و مستقل از context اطرافشون می‌کنه. قوانین کلیدیش: element ها همیشه مستقیم به block وصل می‌شن (نه تو در تو)، از nesting عمیق selector ها پرهیز می‌شه، و نتیجه‌ی نهایی، specificity یکنواخت و کدی که تو تیم‌های بزرگ راحت‌تر قابل‌پیش‌بینیه.