CSS Shadow Parts

CSS Shadow Parts — это механизм, позволяющий стилизовать части веб-компонентов, скрытые внутри их Shadow DOM. В Stencil использование Shadow DOM обеспечивает инкапсуляцию стилей, что предотвращает непреднамеренное влияние глобальных CSS на внутренние элементы компонента. Однако иногда требуется предоставить возможность внешней стилизации отдельных внутренних элементов. Для этого применяются part и ::part().


Объявление части с помощью part

Любой элемент внутри Shadow DOM компонента может быть помечен атрибутом part. Синтаксис:

<div part="header">Заголовок</div>
  • part — имя части, доступной для внешней стилизации.
  • Имя части должно быть уникальным в пределах компонента.
  • Допустимо указывать несколько частей через пробел:
<div part="header title">Заголовок</div>

В Stencil это можно использовать внутри JSX в методе render():

render() {
  return (
    <section>
      <header part="header">Заголовок</header>
      <p part="content">Основной текст</p>
      <footer part="footer">Подвал</footer>
    </section>
  );
}

Стилизация через ::part()

Внешний CSS может нацеливаться на элементы внутри Shadow DOM с помощью селектора ::part(). Синтаксис:

my-component::part(header) {
  font-size: 24px;
  color: #333;
}

my-component::part(content) {
  line-height: 1.6;
}

my-component::part(footer) {
  text-align: center;
  color: #999;
}

Ключевые моменты:

  • Селектор ::part() работает только с элементами, помеченными атрибутом part.
  • Можно использовать несколько частей сразу через запятую:
my-component::part(header),
my-component::part(footer) {
  font-weight: bold;
}

Комбинирование с Shadow DOM и CSS переменными

Stencil поддерживает использование CSS переменных внутри Shadow DOM. При комбинации с ::part() можно создавать гибкие стили, доступные для настройки извне.

render() {
  return (
    <button part="button" style={{ '--btn-color': 'var(--primary-color)' }}>
      Нажми меня
    </button>
  );
}

Внешний CSS:

my-button::part(button) {
  background-color: var(--btn-color);
  padding: 10px 20px;
  border-radius: 5px;
}

Это позволяет пользователям компонента изменять стили через глобальные CSS-переменные, сохраняя инкапсуляцию внутренних элементов.


Множественные уровни ::part() и вложенные компоненты

Если компонент содержит вложенные компоненты с собственным Shadow DOM, селектор ::part() работает только на первом уровне. Чтобы стилизовать внутренние части вложенного компонента, нужно напрямую использовать ::part() на самом вложенном компоненте:

<outer-component>
  <inner-component part="inner"></inner-component>
</outer-component>

CSS:

outer-component::part(inner) {
  margin: 20px;
}

inner-component::part(header) {
  font-size: 18px;
}

Таким образом обеспечивается точечная настройка стилей без нарушения инкапсуляции.


Практические советы

  1. Использовать ясные имена частей. Названия типа header, content, footer легче поддерживать, чем абстрактные part1, part2.
  2. Минимизировать количество внешне доступных частей. Чем меньше частей, тем проще сохранить контроль над визуальной целостностью компонента.
  3. Комбинировать с CSS-переменными для гибкости. Позволяет внешним разработчикам изменять цвета, размеры, отступы без необходимости прямого доступа к Shadow DOM.
  4. Документировать доступные части. Для библиотек компонентов рекомендуется вести список всех part и их назначения.

Поддержка браузеров и ограничения

  • Поддержка part и ::part() есть во всех современных браузерах, включая Chrome, Edge, Firefox и Safari.
  • Старые версии Internet Explorer и некоторых мобильных браузеров не поддерживают этот механизм.
  • ::part() не может нацеливаться на псевдоэлементы (::before, ::after) внутри Shadow DOM.
  • Атрибут part не влияет на доступность или структуру DOM — это чисто стилистический инструмент.

Пример комплексного компонента

@Component({
  tag: 'card-component',
  shadow: true,
  styleUrl: 'card-component.css'
})
export class CardComponent {
  render() {
    return (
      <div class="card">
        <div part="header">Заголовок карты</div>
        <div part="body">
          <slot></slot>
        </div>
        <div part="footer">Подвал карты</div>
      </div>
    );
  }
}

Внешний CSS:

card-component::part(header) {
  font-size: 20px;
  font-weight: bold;
  padding: 10px;
}

card-component::part(body) {
  padding: 15px;
}

card-component::part(footer) {
  font-size: 12px;
  text-align: right;
}

Такой подход позволяет создавать полностью инкапсулированные компоненты с гибкой внешней стилизацией отдельных элементов.