Subgrid؛ این بار عمیق‌تر

تو فصل ۱۳ که درباره‌ی CSS Grid صحبت کردیم، به‌طور مختصر به subgrid اشاره کردیم. اون‌موقع بیشتر روی مفهوم پایه‌ی Grid تمرکز داشتیم. حالا که با Flexbox، Logical Properties و حتی Container Query هم آشنا شدیم، وقتشه یه بار دیگه با دید کامل‌تری سراغ subgrid بریم و ببینیم دقیقاً چه مشکلی رو تو layout های تو در تو حل می‌کنه.

یادآوری سریع: مشکل اصلی چیه؟

فرض کنید یه grid دارید با چند تا کارت داخلش، و هر کارت خودش یه grid فرزند جدا داره (مثلاً برای عنوان، توضیحات و دکمه). مشکل اینجاست: هر کارت، ستون‌بندی یا ردیف‌بندی خودش رو مستقل از grid والد محاسبه می‌کنه. یعنی اگه بخواید عنوان همه‌ی کارت‌ها دقیقاً هم‌تراز باشن (حتی وقتی طول متن‌ها فرق داره)، grid معمولی این امکان رو بهتون نمی‌ده، چون هر کارت دنیای خودش رو داره. این دقیقاً همون چیزیه که subgrid حلش می‌کنه: به یه grid فرزند اجازه می‌ده خط‌بندی (track) های grid والدش رو مستقیماً به ارث ببره.

یادآوری سینتکس پایه

.parent {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto auto;
  gap: 16px;
}

.card {
  grid-row: span 3;
  display: grid;
  grid-template-rows: subgrid;
}

نکته‌ی کلیدی اینجا خط آخره: به‌جای این‌که .card ردیف‌های خودش رو مستقل تعریف کنه، با grid-template-rows: subgrid می‌گه «ردیف‌هام رو از grid والدم به ارث می‌برم».

شرط اجباری: باید span داشته باشه

نکته‌ای که تو فصل ۱۳ شاید به‌اندازه‌ی کافی روش تأکید نشد: برای این‌که subgrid کار کنه، خود المنت (اینجا .card) باید از قبل تو یکی از محورهای grid والد جا گرفته باشه و اون بخش رو با span پوشش بده. یعنی grid-row: span 3; تو مثال بالا اجباریه؛ بدون این، مرورگر نمی‌دونه .card باید کدوم بخش از ردیف‌های والد رو به ارث ببره.

مثال کامل: هم‌تراز کردن عنوان کارت‌ها

این همون مثال کلاسیکیه که subgrid واقعاً توش می‌درخشه:

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  gap: 8px;
}

.card__title {
  grid-row: 1;
}

.card__body {
  grid-row: 2;
}

.card__footer {
  grid-row: 3;
}

با این ساختار، حتی اگه عنوان یه کارت یک خط باشه و عنوان کارت بغلی دو خط، خط شروع .card__body تو همه‌ی کارت‌ها دقیقاً یه‌جا میفته؛ چون همه دارن از همون ردیف‌بندی مشترک والد استفاده می‌کنن، نه ردیف‌بندی مستقل خودشون. این چیزیه که با grid معمولی یا فلکس‌باکس عملاً غیرممکن یا خیلی سخته.

subgrid فقط برای ستون یا فقط برای ردیف هم قابل استفاده‌ست

لازم نیست هر دو محور رو subgrid کنید. می‌تونید مثلاً فقط ردیف‌ها رو به ارث ببرید ولی ستون‌بندی فرزند مستقل بمونه:

.card {
  display: grid;
  grid-template-columns: 1fr 2fr; /* مستقل */
  grid-template-rows: subgrid;    /* به ارث برده‌شده */
}

gap در subgrid: به ارث می‌رسه یا نه؟

یه نکته‌ی ظریف: gap در subgrid به‌طور خودکار از والد به ارث نمی‌رسه، مگر این‌که صراحتاً ننویسیدش؛ یعنی اگه بخواید فاصله‌ی بین ردیف‌های subgrid دقیقاً با فاصله‌ی بین ردیف‌های grid والد یکی باشه، باید gap رو دوباره (یا با مقدار یکسان) روی خود subgrid تعریف کنید.

نکته مهم: اگه المنت subgrid، اسم خط‌ها (line names) رو هم بخواد از والد به ارث ببره، این اتفاق به‌طور خودکار می‌افته؛ یعنی اگه grid والد خط‌هاش رو اسم‌گذاری کرده باشه (مثل [title-start])، فرزند subgrid می‌تونه مستقیماً از همون اسم‌ها استفاده کنه.

فرق subgrid با فقط تنظیم grid-template-columns یکسان روی هر دو

ممکنه فکر کنید می‌شه به‌جای subgrid، همون مقدار grid-template-columns رو دستی هم روی والد هم روی فرزند تکرar کرد و نتیجه‌ی مشابهی گرفت. ولی این کار تو عمل شکننده‌ست: اگه محتوای فرزندها اندازه‌ی ستون‌ها رو کمی جابه‌جا کنه (مثلاً با minmax یا auto)، والد و فرزند ممکنه دیگه هم‌تراز نباشن، چون هرکدوم مستقل محاسبه می‌شن. subgrid این مشکل رو از ریشه حل می‌کنه چون واقعاً همون خط‌بندی رو به اشتراک می‌ذاره، نه یه کپی مشابه ازش.

پشتیبانی مرورگرها

subgrid تو مرورگرهای مدرن (Firefox، Chrome، Safari، Edge نسخه‌های اخیر) پشتیبانی می‌شه، ولی نسبت به grid پایه جدیدتره. اگه پروژه‌تون نیاز به پشتیبانی از نسخه‌های قدیمی‌تر مرورگر داره، حتماً قبل از تکیه کردن روی subgrid تو یه بخش حیاتی طراحی، caniuse رو چک کنید.

اشتباه رایج: فراموش کردن display: grid روی خود فرزند

یه اشتباه رایج اینه که کسی grid-template-rows: subgrid رو می‌نویسه ولی یادش می‌ره خود المنت فرزند هم باید display: grid داشته باشه. بدون این، subgrid اصلاً معنایی نداره چون خود المنت اصلاً یه grid container محسوب نمی‌شه که بخواد چیزی رو از grid دیگه‌ای به ارث ببره.

جمع‌بندی

subgrid به یه grid فرزند اجازه می‌ده به‌جای تعریف مستقل ستون‌ها یا ردیف‌های خودش، مستقیماً خط‌بندی grid والدش رو به ارث ببره؛ چیزی که برای هم‌تراز کردن دقیق عناصر داخل چند کامپوننت تکرارشونده (مثل کارت‌ها) خیلی کاربردیه. نکات کلیدی: المنت باید با span بخشی از grid والد رو اشغال کرده باشه، خودش هم باید display: grid داشته باشه، و gap به‌طور خودکار به ارث نمی‌رسه.