Перейти к содержанию
Innovialabs

Web Components: кастомные элементы и Shadow DOM

Продвинутое3 мин

Коротко

Web Components — набор стандартов браузера для создания собственных HTML-тегов с изолированными стилями; они хороши для дизайн-систем и виджетов, которые должны работать в любом фреймворке.

Что такое Web Components

Web Components — это не библиотека, а набор встроенных в браузер стандартов, которые позволяют создать собственный HTML-тег вроде <price-tag> и использовать его как обычный элемент. Никакого фреймворка для работы не нужно.

Набор состоит из трех частей:

  • Custom Elements — регистрация своего тега и его поведения через JavaScript-класс.
  • Shadow DOM — изолированное поддерево с собственными стилями, которые не протекают наружу и не ломаются от глобального CSS.
  • Templates и slots — заготовки разметки и «окна», куда пользователь компонента вставляет свой контент.

Custom Elements: свой тег за несколько строк

Элемент описывается классом, который наследуется от HTMLElement, и регистрируется через customElements.define. Имя обязательно должно содержать дефис.

class PriceTag extends HTMLElement {
  static observedAttributes = ["amount"];

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback() {
    this.render();
  }

  render() {
    const amount = Number(this.getAttribute("amount") || 0);
    this.textContent = amount.toLocaleString("ru-RU") + " сум";
  }
}

customElements.define("price-tag", PriceTag);

Основные хуки жизненного цикла:

  • connectedCallback — элемент добавлен в документ;
  • disconnectedCallback — удален (здесь снимают слушатели и таймеры);
  • attributeChangedCallback — изменился атрибут из списка observedAttributes.

Shadow DOM: изоляция стилей

Shadow DOM создает внутри элемента отдельное дерево. Стили внутри него не влияют на страницу, а стили страницы почти не влияют на него.

class UiCard extends HTMLElement {
  constructor() {
    super();
    const root = this.attachShadow({ mode: "open" });
    root.innerHTML = `
      <style>
        :host { display: block; border-radius: 12px; padding: 16px; }
        ::slotted(h3) { margin: 0; }
      </style>
      <slot name="title"></slot>
      <slot></slot>
    `;
  }
}
customElements.define("ui-card", UiCard);

Что важно знать на практике:

  • Наследуемые свойства (color, font-family) проходят внутрь — это удобно для темы.
  • CSS custom properties (--brand-color) тоже проходят сквозь границу — это основной способ кастомизации.
  • Для точечной стилизации снаружи есть ::part() — если автор компонента пометил элемент атрибутом part.

Templates и slots

Тег <template> хранит разметку, которая не рендерится, пока ее не клонируют. Слоты определяют, куда попадет контент, переданный внутрь компонента:

<ui-card>
  <h3 slot="title">Тариф «Старт»</h3>
  <p>Описание тарифа</p>
</ui-card>

Заголовок попадет в именованный слот title, остальное — в слот по умолчанию.

Совместимость с фреймворками

Web Components работают везде, где есть DOM, но есть нюансы:

АспектЧто учитывать
АтрибутыВсегда строки; сложные данные лучше передавать через свойства
СобытияКомпонент отправляет CustomEvent, фреймворк подписывается на него
ReactСовременные версии лучше передают свойства и события в custom elements; в старых нужны обертки
Vue, Angular, SvelteПоддерживают custom elements хорошо, иногда нужно явно указать, какие теги кастомные
SSRShadow DOM рендерится на клиенте; для сервера есть Declarative Shadow DOM, но экосистема еще развивается

Для событий наружу используйте bubbles: true и composed: true, иначе событие не выйдет за границу Shadow DOM.

Когда Web Components имеют смысл

Подходят, если:

  • нужна дизайн-система, которой пользуются команды на разных фреймворках;
  • вы делаете встраиваемый виджет (чат, форма, калькулятор) для чужих сайтов;
  • нужен долгий срок жизни компонентов без привязки к версии фреймворка;
  • проект — сайт на CMS или серверных шаблонах, где тяжелый фреймворк избыточен.

Не лучший выбор, если все приложение уже на одном фреймворке: его собственные компоненты дают удобнее состояние, роутинг и SSR. Для сложных интерфейсов чаще берут легкие библиотеки поверх стандарта, например Lit, — они убирают ручной рендеринг.

Частые ошибки

  • Тяжелая работа в constructor вместо connectedCallback.
  • Передача объектов через атрибуты с JSON.stringify.
  • Забытая очистка слушателей в disconnectedCallback.
  • Отсутствие доступности: у кастомной кнопки нет роли, фокуса и обработки клавиатуры.
  • Повторный вызов customElements.define с тем же именем — это вызывает ошибку.

Частые вопросы

Нет. Custom element можно сделать и без Shadow DOM — тогда на него действуют глобальные стили. Shadow DOM нужен, когда важна изоляция, например во встраиваемых виджетах.

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.