Теневое дерево 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) проходят через границу тени. Это делает пользовательские свойства основным API для тем: предоставляйте их с разумными резервными вариантами.
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
Пользовательское свойство, определенное в :host, может быть переопределено только правилами, предназначенными для элемента 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() и exportparts: показ внутренних элементов для тем
Пометьте внутренний элемент с помощью 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в настоящее время является единственным общим способом передачи произвольных стилей обернутому нативному элементу без изучения потребителями частей каждого компонента или дополнительных параметров, а удаление собственного блока хоста обычно является смыслом шаблона (композиция компенсирует отсутствие встроенного наследования). Два реальных предостережения:all: inheritстирает значения по умолчанию UA, поэтому ограничьте область действия определенными свойствами или используйтеrevert, если вы хотите вернуть значения по умолчанию; и конкретный блокировщик —getBoundingClientRect()иgetClientRects()возвращают ноль для элементовdisplay: contents, поэтому позиционирование привязки относительно хоста нарушено. Если эти компромиссы не подходят, используйте::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 и не является перспективным.