Web Components: template и slot

Web Components — набор нативных браузерных API для создания переиспользуемых кастомных HTML-элементов с инкапсулированной логикой, стилями и разметкой.

Зачем нужно

Web Components позволяют создать компонент (<my-card>, <user-avatar>) без React/Vue/Angular и использовать его в любом фреймворке или в чистом HTML. Три ключевых стандарта: Custom Elements (новые HTML-теги), Shadow DOM (изолированное дерево), HTML Templates (<template> + <slot> для вставки контента).

Где используется

  • Дизайн-системы с компонентами, независимыми от фреймворка
  • Виджеты, встраиваемые на сторонние сайты
  • Микрофронтенды с разными технологиями
  • Кастомные UI-элементы (date picker, modal, tab panel)

<template> — инертная разметка

Содержимое <template> не рендерится при загрузке страницы и не выполняет скрипты. Это «заготовка»:

<template id="card-template">
  <div class="card">
    <img class="card-img" src="" alt="">
    <div class="card-body">
      <h3 class="card-title"></h3>
      <p class="card-text"></p>
    </div>
  </div>
</template>

<script>
  const template = document.getElementById('card-template');
  const clone = template.content.cloneNode(true);

  clone.querySelector('.card-title').textContent = 'MacBook Pro';
  clone.querySelector('.card-text').textContent = '189 990 ₽';

  document.body.appendChild(clone);
</script>

<slot> — точки вставки в Shadow DOM

<slot> позволяет передавать контент снаружи внутрь Shadow DOM компонента:

<!-- Определение компонента -->
<template id="info-box-template">
  <style>
    .box { border: 1px solid #ccc; padding: 16px; border-radius: 8px; }
    .title { font-weight: bold; margin-bottom: 8px; }
  </style>
  <div class="box">
    <div class="title">
      <slot name="title">Заголовок по умолчанию</slot>
    </div>
    <slot>Контент по умолчанию</slot>
  </div>
</template>

<script>
  class InfoBox extends HTMLElement {
    constructor {
      super;
      const shadow = this.attachShadow({ mode: 'open' });
      const template = document.getElementById('info-box-template');
      shadow.appendChild(template.content.cloneNode(true));
    }
  }
  customElements.define('info-box', InfoBox);
</script>

<!-- Использование компонента в HTML -->
<info-box>
  <span slot="title">Заголовок карточки</span>
  <p>Это содержимое, которое попадёт в дефолтный slot.</p>
</info-box>

Именованные и безымянные slots

<!-- Шаблон компонента -->
<template id="user-card">
  <div class="user-card">
    <slot name="avatar"></slot>      <!-- именованный -->
    <div class="info">
      <slot name="name"></slot>      <!-- именованный -->
      <slot></slot>                  <!-- безымянный — всё остальное -->
    </div>
  </div>
</template>

<!-- Использование -->
<user-card>
  <img slot="avatar" src="avatar.jpg" alt="Аватар">
  <strong slot="name">Иван Иванов</strong>
  <p>Frontend Developer</p>         <!-- попадёт в безымянный slot -->
</user-card>

Поддержка браузеров

Все современные браузеры поддерживают <template>, <slot> и Custom Elements. IE не поддерживает (но IE мёртв).

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

Ошибка Почему плохо Как правильно
Стили Shadow DOM не наследуются от родителя Инкапсуляция — это фича, но нужны CSS Custom Properties Используй var(--color) для настройки извне
mode: 'closed' для Shadow DOM Невозможно тестировать и отлаживать Используй mode: 'open'
<template> без JS — контент не показывается <template> инертен без клонирования Клонируй и вставляй через JS
customElements.define дважды с одним тегом Ошибка в рантайме Проверяй customElements.get('tag-name')

Связанные темы

Ресурсы