در یک پروژه‌ی بازطراحی، وقتی کدهای CSS قدیمی را باز کردم، به یک الگوی تکرارشونده برخوردم: به ازای هر عنصر، یک کلاس جداگانه ساخته شده بود. دکمه‌ی آبی، دکمه‌ی قرمز، دکمه‌ی سبز، دکمه‌ی حاشیه‌دار؛ همه کلاس‌های جدا با قواعد تقریباً یکسان. دلیلش را که پرسیدم، توسعه‌دهنده گفت «انتخابگرهای CSS را بلد نبودم که چطور این‌ها را با هم گروه کنم». آن روز به من ثابت شد که انتخابگرهای CSS (CSS Selectors) نه یک جزئیات زبان، بلکه مهم‌ترین ابزاری هستند که تفاوت میان CSS بلد بودن و CSS فهمیدن را می‌سازند.

انتخابگرها به شما اجازه می‌دهند بدون افزودن یک کلاس اضافه، عنصر دقیق موردنظرتان را هدف بگیرید و رفتارش را کنترل کنید. اگر در مسیر آموزش CSS از صفر هستید یا از قبل با CSS کار می‌کنید، این نوشته نقشه‌ی جامعی است که همه‌ی انواع انتخابگرها، دام‌ها و الگوهای واقعی آن‌ها را پوشش می‌دهد.

چرا انتخابگرها این‌قدر مهم‌اند؟

انتخابگر، زبان مشترک بین ساختار HTML و ظاهر CSS است. اگر این زبان را دقیق بلد نباشید، دو مسیر پیش رو دارید: یا کلاس‌های اضافه در HTML می‌سازید (که نگهداری را سخت می‌کند)، یا با انتخابگرهای نادرست کار می‌کنید و بجای اینکه استایل هدف را بگیرد، ناخواسته عناصر دیگر را هم تحت تأثیر قرار می‌دهید.

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

برای اینکه ببینید این مفاهیم در ساختار بزرگ‌تر CSS کجا قرار می‌گیرند، کنار این مقاله استانداردهای HTML و CSS را هم مرور کنید.

یک انتخابگر خوب، آن است که اگر HTML فردا تغییر کند، باز هم به هدفش بخورد؛ یک انتخابگر شکننده، آن است که فقط با ساختار فعلی کار می‌کند.

انتخابگرهای پایه: نقطه‌ی شروع

پنج انتخابگر پایه وجود دارد که بقیه‌ی قدرت انتخابگرها روی همین‌ها ساخته می‌شود:

  • نوع (Type): p، h1، button — همه‌ی عناصر آن نوع را هدف می‌گیرد.
  • کلاس (Class): .card — همه‌ی عناصری که این کلاس را دارند. پرکاربردترین و انعطاف‌پذیرترین.
  • شناسه (ID): #header — تنها یک عنصر. specificity بسیار بالا؛ کمتر استفاده کنید.
  • عمومی (Universal): * — همه‌ی عناصر. برای box-sizing سراسری یا ریست.
  • گروه‌بندی (Grouping): h1, h2, h3 — چند انتخابگر با یک قاعده.

برای مطالعه‌ی عمیق‌تر این پایه‌ها، اگر تازه شروع کرده‌اید، آموزش CSS از صفر نقطه‌ی شروع منطقی است.

ترکیب‌گرها و منطق رابطه‌ها

ترکیب‌گرها به شما اجازه می‌دهند رابطه‌ی بین دو عنصر را توصیف کنید. چهار ترکیب‌گر استاندارد وجود دارد:

ترکیب‌گرسینتکسمعنی
فرزند مستقیم.card > .titleعنصری که فرزند مستقیم است
نوه و نتیجه.card .titleهر عمقی داخل کارت
خواهر بلافاصله بعدیh2 + pفقط پاراگراف بعد از تیتر
همه‌ی خواهرهای بعدیh2 ~ pهمه‌ی پاراگراف‌های بعدی

یک نکته‌ی عملی که در پروژه‌ها به‌کارم آمده: هر جا از ترکیب‌گر > استفاده کنید، انتخابگر مقاوم‌تر می‌شود. چون در آینده اگر ساختار عمیق‌تر شود، باز هم به هدف می‌خورد و نوه‌ها را ناخواسته تحت تأثیر نمی‌گیرد.

الگوی خطرناک، استفاده‌ی بی‌رویه از فضای ساده است: .a .b .c .d. این نوع انتخابگر هم کند است و هم شکننده. اگر در پروژه‌ای زیاد از این‌ها می‌بینید، احتمالاً معماری CSS آن پروژه نیاز به بازبینی دارد.

انتخابگرهای ویژگی؛ دقیق و بدون کلاس اضافه

یکی از کم‌استفاده‌شده‌ترین امکانات CSS، انتخابگرهای ویژگی هستند. این‌ها به شما اجازه می‌دهند عناصری را هدف بگیرید که یک ویژگی مشخص دارند یا مقدارشان الگوی خاصی را دنبال می‌کند. کاربرد عملی‌شان وقتی است که نمی‌خواهید به HTML کلاس اضافه کنید یا با HTML تولیدی (مثل خروجی قالب وردپرس) کار می‌کنید.

/* هر input از نوع email */
input[type="email"] {
  border-color: #0055ff;
}

/* لینک‌های خارجی */
a[href^="http"] {
  color: #ff6600;
}

/* فایل‌های PDF */
a[href$=".pdf"]::after {
  content: " (PDF)";
}

سه حالت پرکاربرد: ^= (شروع با)، $= (پایان با)، *= (شامل). موردی که در پروژه‌های فارسی به‌کارم آمده: علامت‌گذاری خودکار لینک‌های خارجی برای مخاطب فارسی، بدون نیاز به تغییر محتوا.

شبه‌کلاس‌ها: حالات و موقعیت‌ها

شبه‌کلاس‌ها با : شروع می‌شوند و عنصری را هدف می‌گیرند که در وضعیت خاصی است. سه دسته‌ی پرکاربردشان:

شبه‌کلاس‌های تعاملی

:hover، :focus، :active، :focus-visible، :visited. این‌ها رفتار عنصر در لحظه‌ی تعامل کاربر را تعریف می‌کنند. نکته‌ی مهم: :focus-visible نسبت به :focus تجربه‌ی بهتری می‌دهد چون فقط برای کاربران کیبورد فعال می‌شود و کلیک ماوس را نادیده می‌گیرد.

شبه‌کلاس‌های ساختاری

:first-child، :last-child، :nth-child()، :nth-of-type()، :only-child. این‌ها را برای الگوهایی مثل راه‌راه کردن جدول‌ها، حذف حاشیه‌ی آخرین آیتم لیست، یا انتخاب ستون‌های خاص به‌کار می‌برم.

tr:nth-child(2n) {
  background: #f9f9f9;
}

li:not(:last-child) {
  border-bottom: 1px solid #eee;
}

شبه‌کلاس‌های منطقی

:not()، :is()، :where()، :has(). این‌ها که نسل جدیدی هستند، در بخش انتخابگرهای مدرن مفصل توضیح داده می‌شوند.

برای مطالعه‌ی همه‌ی شبه‌کلاس‌ها در قالب یک مرجع سریع، ترفندهای CSS برای طراحی سریع‌تر را ببینید.

شبه‌عناصر: المان‌هایی که وجود ندارند

شبه‌عناصر با :: شروع می‌شوند و به شما اجازه می‌دهند بخشی از یک عنصر یا محتوایی که در HTML نیست را استایل دهید:

  • ::before و ::after — برای افزودن محتوای تزئینی.
  • ::first-line و ::first-letter — برای استایل‌دهی بخش اول متن.
  • ::placeholder — برای استایل متن راهنمای input.
  • ::selection — برای استایل متن انتخاب‌شده.
.quote::before {
  content: "«";
  font-size: 2rem;
  color: #999;
}

input::placeholder {
  color: #aaa;
  font-style: italic;
}

یک نکته‌ی مهم: محتوای داخل content برای screen readerها خوانده می‌شود؛ اگر متن تزئینی‌ست، بهتر است آن را در HTML نگذارید و از aria-hidden یا استراتژی دیگری استفاده کنید. برای مطالعه‌ی بیشتر، استانداردهای دسترس‌پذیری وب را بخوانید.

Specificity و ترتیب برنده‌شدن

وقتی چند قاعده روی یک عنصر اثر می‌گذارند، مرورگر با قاعده‌ی مشخصی تصمیم می‌گیرد کدام برنده است. این قاعده را specificity می‌گویند و به شکل چهار عدد نمایش داده می‌شود: (a, b, c, d).

  • a: استایل‌های inline.
  • b: تعداد IDها.
  • c: تعداد کلاس‌ها، ویژگی‌ها، شبه‌کلاس‌ها.
  • d: تعداد عناصر و شبه‌عناصر.

مثال:

انتخابگرSpecificity
p0, 0, 0, 1
.card0, 0, 1, 0
#header0, 1, 0, 0
a:hover0, 0, 1, 1
.card .title a0, 0, 2, 1

حالا نکته‌ی کاربردی: در پروژه‌ها دیده‌ام که اگر از ابتدا همه‌ی قواعد خود را با specificity پایین بنویسید، تقریباً هیچ‌وقت نیاز به !important پیدا نمی‌کنید. این یک تصمیم معماری ساده است که در طول عمر پروژه، پول و اعصاب زیادی ذخیره می‌کند.

هر !important که در کد می‌نویسید، یک بدهی فنی است که خودتان دو سال بعد می‌پردازید.

انتخابگرهای مدرن: :has، :is، :where

سه شبه‌کلاس منطقی که در سه سال اخیر تحول بزرگی در نوشتن CSS ایجاد کرده‌اند:

:is() — گروه‌بندی با کمترین specificity

جای گروه‌بندی دستی چند انتخابگر، یک‌بار می‌نویسید:

/* قبل */
h1 a, h2 a, h3 a, h4 a { color: inherit; }

/* بعد */
:is(h1, h2, h3, h4) a { color: inherit; }

مزیت کلیدی: :is() specificity بالاترین مقدار بین گزینه‌ها را می‌گیرد، نه جمع آن‌ها. یعنی انتخابگر شما کوتاه‌تر است، اما specificity پایین‌تری نسبت به معادل طولانی دارد.

:where() — با specificity صفر

همان منطق :is()، اما specificity آن صفر است. این برای ریست‌ها یا استایل‌هایی که می‌خواهید به‌راحتی override شوند، بی‌نظیر است.

:has() — انتخاب بر اساس محتوا

انقلابی‌ترین انتخابگر سال‌های اخیر. حالا می‌توانید عنصری را هدف بگیرید که یک فرزند خاص دارد:

/* کارتی که تصویر دارد */
.card:has(img) {
  padding: 0;
}

/* فرمی که خطای اعتبارسنجی دارد */
form:has(input:invalid) {
  border-color: red;
}

پیش از :has()، این نوع شرطی‌بودن فقط با جاوااسکریپت ممکن بود. امروز در پروژه‌های تازه، به‌جای نوشتن کلاس‌های کمکی اضافه در HTML، مستقیم از :has() استفاده می‌کنم. پشتیبانی مرورگرها امروز به حدی رسیده که در پروژه‌های معمولی نگرانی ندارد، اما برای مخاطب بسیار قدیمی توصیه می‌کنم به تست چیدمان واکنش‌گرا در مرورگرها نگاهی بیندازید.

کارایی انتخابگرها در مرورگر

پرسشی که در جلسه‌های فنی زیاد می‌شنوم: «آیا انتخابگر پیچیده، سایت را کند می‌کند؟» پاسخ کوتاه: در سایت‌های معمولی نه، اما در سه سناریو محسوس می‌شود.

  1. در DOMهای بسیار بزرگ: هر انتخابگر باید کل درخت را جستجو کند، مگر اینکه با کلاس محدودش کنید. یک انتخابگر مثل .sidebar li a سریع‌تر از .sidebar * a است.
  2. با :has() و :nth-child()های پیچیده: این‌ها ذاتاً سنگین‌تر از انتخابگرهای ساده‌اند. در لیست‌های ده‌هزارتایی، تفاوتش قابل اندازه‌گیری است.
  3. در انیمیشن: اگر انتخابگر در حین انیمیشن تغییر کند و مرورگر مجبور شود مکرر آن را resolve کند، بار روی rendering می‌رود.

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

اشتباهاتی که در پروژه‌ها دیدم

  • استفاده‌ی بی‌رویه از !important: خودش را در دام specificity می‌اندازد. هر بار این کلمه را نوشتید، به‌جای آن به ساختار انتخابگرها برگردید.
  • شناسه برای استایل: #header .nav a specificity بالایی می‌سازد که باعث می‌شود بعداً برای override، مجبور به نوشتن کد پیچیده‌تر شوید. کلاس‌ها آزادی بیشتری می‌دهند.
  • انتخابگرهای بیش‌ازحد عمیق: .page .content .post .body p شکننده است. اگر یک لایه از ساختار حذف شود، کل قاعده کار نمی‌کند.
  • انتخابگرهایی که به ترتیب HTML وابسته‌اند: .header + .banner فقط با ترتیب فعلی کار می‌کند. با اولین تغییر مارک‌آپ، همه چیز می‌شکند.
  • نادیده گرفتن :focus-visible: اگر outline را کامل حذف کنید بدون جایگزین، کاربران کیبورد نمی‌توانند در سایت شما حرکت کنند.

لایه‌ای پایین‌تر از سینتکس

اگر می‌خواهید به لایه‌ی مهندسی انتخابگرها نفوذ کنید، سه مفهوم را بشناسید:

  1. Selector Matching از راست به چپ: موتور CSS ابتدا آخرین بخش انتخابگر را جستجو می‌کند، بعد به سمت چپ می‌رود. یعنی .sidebar a از aها شروع می‌کند. این توضیح می‌دهد چرا انتخابگرهای عمیق کندترند: موتور باید همه‌ی aها را پیدا کند و بعد بررسی کند کدام‌شان در .sidebar هست.
  2. Selector Matching Engine و Bloom Filter: مرورگرها برای بهینه‌سازی، از ساختارهای داده مثل Bloom Filter روی کلاس‌ها و IDها استفاده می‌کنند. این‌ها سریع‌اند اما در سایت‌های با کلاس‌های بی‌شماری که هر کدام به‌طور متوسط یک‌بار استفاده شده‌اند، کارایی کمتری دارند.
  3. Style Recalculation در React و Vue: وقتی در فریم‌ورک‌هایی مثل React از inline style یا CSS-in-JS استفاده می‌کنید، هر رندر ممکن است کل درخت CSS را recalculate کند. اگر در یک تیم از این رویکرد استفاده می‌کنید، بهینه سازی جاوااسکریپت را در کنار این مقاله بخوانید.

نکته‌ی عملی برای مهندسان پلتفرم: اگر در پروژه‌ای با هزاران انتخابگر سروکار دارید و مشغول بازبینی کارایی هستید، اول با Chrome DevTools → Performance ضبط کنید، بعد دنبال بخش Recalculate Style بگردید. تعداد رخدادهای این بخش، مهم‌تر از پیچیدگی هر انتخابگر است. اگر این عدد بالا باشد، راه‌حل معمولاً محدود کردن انتخابگرها نیست؛ حذف کلاس‌های بی‌استفاده در HTML است.

انتخابگرها ابزار کنترل شما هستند، نه ابزار قدرت‌نمایی؛ هر چه ساده‌تر و مستقیم‌تر، پایدارتر.

برای مطالعه‌ی موازی این لایه با جاوااسکریپت که با هم روی DOM کار می‌کنند، کار با DOM در جاوااسکریپت و مفاهیم پیشرفته جاوااسکریپت را ببینید.

حرف آخر این نقشه

انتخابگرهای CSS بیش از یک مجموعه سینتکس، یک ذهنیت هستند. اگر آن‌ها را درست بفهمید، سه اتفاق می‌افتد:

  1. HTML ساده‌تر می‌شود: بدون نیاز به کلاس‌های کمکی فراوان.
  2. کد CSS پایدارتر می‌شود: با specificity پایین، override کردن آسان است.
  3. نگهداری سریع‌تر می‌شود: بدون !important و انتخابگرهای شکننده.

اگر در پروژه‌ای هستید که با انتخابگرهای پیچیده دست‌وپنجه نرم می‌کنید، تجربه‌ی دقیق خودتان را در دیدگاه بنویسید. به‌خصوص اگر با یک انتخابگر تازه مثل :has() موفق به حذف یک بلاک جاوااسکریپت شده‌اید؛ این نوع نکات، برای خواننده‌ی بعدی از هر مقدمه‌ای ارزشمندتر است. 🧭