سبک‌بندی عناصر وب

Shadow DOM یک مرز سبک است: قوانین صفحه به داخل نمی‌رسند و قوانین مؤلفه به بیرون نشت نمی‌کنند، به‌جز ویژگی‌های به‌ارث‌رسیده (ازجمله ویژگی‌های سفارشی). ازطریق کانال‌های زیر، آن را به‌طور عمدی رد کنید.

:host، :host()، :host-context()

/* The component's own host element, styled from inside its shadow tree. */
:host { display: block; }

/* Only when the host matches a selector (e.g. a reflected state attribute). */
:host([disabled]) { opacity: 0.5; pointer-events: none; }

/* Based on an ancestor in the Light DOM; useful for theming. */
:host-context([theme="dark"]) { background: #111; color: #eee; }

از :host-context() اجتناب کنید. این ویژگی برای آزمایش اجداد به خارج از مرز می‌رسد، اما Firefox و Safari از ارائه آن خودداری کرده‌اند و CSSWG تمایل دارد آن را بیشتر توسعه ندهد — آن را به‌عنوان یک بن‌بست مختص Chromium درنظر بگیرید، نه یک گزینه آینده‌نگر. به‌جای آن، ازطریق یک ویژگی سفارشی CSS موروثی (در زیر) زمینه را تنظیم کنید: مصرف‌کننده ویژگی را روی یک عنصر والد تنظیم می‌کند و مؤلفه آن را در سراسر مرز می‌خواند، که در همه جا کار می‌کند و هر دو طرف را جدا نگه می‌دارد.

ویژگی‌های موروثی و ویژگی‌های سفارشی از مرز عبور می‌کنند

ویژگی‌های CSS موروثی (color، font-family، line-height، و ویژگی‌های سفارشی CSS) از مرز سایه عبور می‌کنند. این امر باعث می‌شود ویژگی‌های سفارشی به میانای برنامه‌سازی کاربردی اصلی زمینه‌سازی تبدیل شوند: آن‌ها را با جایگزین‌های منطقی نمایش دهید.

:host {
  /* Consumers override --card-bg from outside; the fallback keeps it usable. */
  background: var(--card-bg, white);
  color: var(--card-fg, black);
}

ویژگی سفارشی تعریف‌شده در :host فقط می‌تواند با قوانینی که به‌طور خاص عنصر میزبان را هدف قرار می‌دهند لغو شود. برای اینکه اجازه دهید این ویژگی به ارث برده شود و از هرجایی در درخت قابل‌لغو باشد، ویژگی عمومی را در :host تنظیم‌نشده رها کنید و به مقدار بازگشتی در هر var() تکیه کنید؛ ویژگی عمومی را در :host مقداردهی اولیه نکنید.

برای اینکه مقدار بازگشتی یک‌بار اعلام شود و در هر var() تکرار نشود، یک ویژگی «سایه» خصوصی در :host تعریف کنید که ویژگی عمومی را بخواند و به‌صورت داخلی به ویژگی خصوصی ارجاع دهد:

:host {
  --_card-bg: var(--card-bg, white);
  --_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }

مصرف‌کنندگان همچنان --card-bg را از بیرون لغو می‌کنند؛ زیرخط پیشرو نشان می‌دهد که نام مستعار داخلی است و بازگشت دقیقاً در یک مکان قرار دارد.

::slotted(): سبک‌دهی Light DOM پیش‌بینی‌شده

::slotted(selector) گره‌های سطح بالا اختصاص‌داده‌شده به یک جایگاه را هدف قرار می‌دهد. نمی‌تواند به فرزندان آن برسد و دربرابر هر قانون Light DOM (مصرف‌کننده مالک محتوای خودش است) بازنده است، بنابراین از آن برای رتوش‌های جزئی استفاده کنید، نه کنترل. ::slotted(...) !important می‌تواند قوانین مصرف‌کننده را شکست دهد، اما انجام این کار سبک‌بندی‌ای را که مصرف‌کننده حق دارد داشته باشد لغو می‌کند — آن را برای اصلاحات ساختاری نگه دارید، نه لغوهای زیبایی‌شناختی.

/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }

::part() and exportparts: expose internals for theming

یک عنصر داخلی را با part="name" علامت‌گذاری کنید تا مصرف‌کنندگان بتوانند آن را با هر ویژگی‌ای سبک‌دهی کنند، بدون اینکه نیاز باشد ساختار داخلی شما را یاد بگیرند.

<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }

چند بخش سطح بالا (دکمه، ورودی، برچسب) را نمایش دهید، نه همه <div>؛ سطوح بخش‌های گسترده به API شکننده تبدیل می‌شوند. برای اینکه بخش‌های عنصر تودرتو را دوباره ازطریق عنصر خودتان به‌سمت بالا نمایان کنید، آن‌ها را با exportparts بازارسال کنید:

<inner-widget exportparts="submit: card-submit"></inner-widget>

بین این دو کانال آگاهانه انتخاب کنید: ویژگی‌های سفارشی برای مجموعه‌ای از مقادیر محدود و ایمن ازنظر سیستم طراحی؛ بخش‌ها برای سبک‌بندی نامحدود یک عنصر خاص.

درحال بازارسال سبک‌ها به عنصر بومی پیچیده

وقتی عنصر سفارشی کنترل بومی را می‌پوشاند (<my-button> اطراف <button> واقعی)، سه چیز معمولاً باعث اشتباه افراد می‌شود:

  • کنترل‌های فرم از تایپوگرافی ارث‌بری نمی‌کنند. color و ویژگی‌های سفارشی از مرز عبور می‌کنند، اما <button>، <input>، و <select> font و موارد مشابه را از برگه سبک نگارش UA می‌گیرند که به ارث نمی‌رسد. بنابراین، کنترل پیچیده‌شده در قلم پلاتفرم (نه قلم صفحه) ارائه می‌شود تا زمانی که font: inherit (و معمولاً color: inherit) را روی آن در درخت سایه تنظیم کنید. این رایج‌ترین اشتباه است.
  • سبک‌های :host به عنصر داخلی نمی‌رسند. تنظیم background/border/padding در :host به جعبه میزبان سبک می‌دهد، نه به کنترل داخل آن. ارسال عامدانه: کنترل را به‌عنوان ::part برای سبک‌بندی باز نشان دهید، یا مجموعه‌ای ثابت از مقادیر را ازطریق ویژگی‌های سفارشی هدایت کنید — قانون :host به‌صورت آبشاری به عنصر پیچیده‌شده اعمال نمی‌شود.
  • display: contents + all: inherit یک معاوضه واقعی است، نه یک میان‌بر. درحال‌حاضر، جمع کردن میزبان با display: contents و بازنشانی کنترل داخلی با all: inherit تنها روش عمومی برای انتقال سبک‌های دلخواه به عنصر بومی پیچیده‌شده است، بدون اینکه مصرف‌کنندگان بخش‌های مربوط به هر مؤلفه یا ویژگی‌های سفارشی را یاد بگیرند — و برداشتن جعبه خود میزبان معمولاً هدف این الگو است (ترکیب‌بندی جبران‌کننده وراثت توکار مفقودشده است). Two real caveats: all: inherit wipes UA defaults, so scope to specific properties or use revert when you want defaults back; and the concrete blocker — getBoundingClientRect() and getClientRects() return zero for display: contents elements, so anchor positioning against the host is broken. وقتی این موازنه‌ها مناسب نیستند، از ::part یا ویژگی‌های سفارشی استفاده کنید.

:state(): سبک‌دهی به حالت‌های خود مؤلفه

عنصر سفارشی می‌تواند حالت‌های منطقی داخلی را ازطریق ElementInternals بااستفاده از this.#internals.states.add('busy') و .delete('busy') نمایش دهد (برای استفاده از چرخه حیات، به عناصر سفارشی مراجعه کنید)، و این حالت‌ها به کانال سبک‌دهی در سراسر مرز تبدیل می‌شوند — مانند ::part و ویژگی‌های سفارشی، اما برای حالت به‌جای ساختار یا مقادیر. آن‌ها را در درخت سایه با :host(:state(name)) و از برگه سبک نگارش مصرف‌کننده با tag-name:state(name) مطابقت دهید:

/* Inside the component's shadow tree. */
:host(:state(busy)) button { opacity: 0.6; pointer-events: none; }

/* From the consumer's stylesheet — a read-only public hook. */
favorite-button:state(favorited) { filter: drop-shadow(0 0 4px crimson); }

برخلاف مشخصه بازتابی، حالت سفارشی برای بیرون فقط خواندنی است: مصرف‌کنندگان می‌توانند به آن سبک بدهند اما نمی‌توانند آن را تنظیم یا جعل کنند، بنابراین مؤلفه تنها نویسنده باقی می‌ماند. هرگاه وضعیت متعلق به مؤلفه باشد و فقط نیاز به سبک‌دهی داشته باشد، آن را بر مشخصه data-* ترجیح دهید.

@layer و پُرسمان‌های محتوا در مؤلفه‌ها

  • لایه‌های آبشاری (@layer) در ریشه سایه کار می‌کنند و به آن محدود می‌شوند؛ نام لایه در درخت سایه مستقل از لایه هم‌نام در صفحه است. از آن‌ها برای سفارش دادن سبک‌های مؤلفه خودتان (مثلاً @layer base, theme;) استفاده کنید تا لغوهای مصرف‌کننده ازطریق ویژگی‌های سفارشی و ::part همچنان به‌طور قابل‌پیش‌بینی برنده شوند.
  • پرس‌وجوهای محفظه ابزار مناسبی برای مؤلفه‌های واکنش‌گرا هستند: یک مؤلفه باید به فضایی که به آن داده شده است پاسخ دهد، نه به نمای هدف. میزبان یا بسته‌بندی را به‌عنوان محتوا اعلام کنید (به :host یک جعبه چیدمان مانند display: block بدهید، زیرا عناصر سفارشی به‌طور پیش‌فرض به display: inline تبدیل می‌شوند و محدودیت اندازه برای عناصر inline یا contents اعمال نمی‌شود) و آن را جستجو کنید تا همان مؤلفه در نوار کناری یا منطقه تمام‌عرض سازگار شود.
:host {
  display: block; /* Required: size containment does not apply to default display: inline */
  container-type: inline-size;
}

@container (min-width: 30rem) {
  .card { grid-template-columns: 1fr 2fr; }
}

راهبردهای جایگزین

::part، لایه‌های آبشاری، و پُرسمان‌های محتوی به‌طور گسترده دردسترس هستند؛ زمینه عمدتاً ازطریق ویژگی‌های سفارشی موروثی که حتی در جاهایی که این‌ها کار نمی‌کنند کار می‌کنند. همان‌طور که در بالا اشاره شد، به :host-context() اتکا نکنید — این گزینه درواقع فقط برای Chromium است و گزینه‌ای آینده‌نگرانه نیست.