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') |