HTML Templates and Slots for Web Components
We implement Web Components using HTML Templates and Slots for projects that need framework-agnostic UI components. Our team builds reusable custom elements that work in any modern framework or plain HTML context, with proper Shadow DOM encapsulation, named and default slots, and TypeScript class definitions. Delivery takes one to five working days depending on component count. We have built component libraries for design systems, portals, and dashboard applications. Our components include full TypeScript types, automated tests, and usage documentation for your development team.
HTML Templates and Slots are native browser features that complement Custom Elements and Shadow DOM. Templates let you define component markup in HTML without executing it until needed. Slots let consumers project their own content into the component's Shadow DOM. Together they form the foundation of framework-independent component systems.
What's Included in Our Web Components Service
We deliver Web Component implementations as a turnkey package. The scope covers:
- Custom element class definitions in TypeScript with Shadow DOM attachment
- HTML Template markup with named slots and fallback content
- Slot change event handling for reactive slot content updates
- Attribute observation and property reflection for external API
- CSS custom property theming support via
:hostselectors - Component documentation with usage examples
- Automated tests covering rendering, slot projection, and attribute changes
Why Use HTML Templates Instead of JavaScript Strings?
Browser performance differs between the two approaches. A JavaScript string assigned to innerHTML is parsed by the browser on every element instance creation. An HTML Template is parsed once when the page loads. Cloning the template's content creates a new DOM fragment without re-parsing.
For components that render hundreds of instances on a page, such as product cards or list items, this difference matters. Template cloning is faster because the parse work happens once, not per instance.
HTML Template with Named Slots
<template id="card-template"> <style> .card { padding: 24px; border-radius: 12px; background: var(--card-bg, #fff); box-shadow: 0 2px 16px rgba(0,0,0,0.08); } .card__header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .card__avatar { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; } </style> <div class="card"> <div class="card__header"> <img class="card__avatar" src="" alt=""> <div class="card__meta"> <slot name="name"><strong>Name not specified</strong></slot> <slot name="role"><em>Role not specified</em></slot> </div> </div> <div class="card__body"> <slot>Description not specified</slot> </div> </div> </template> class TeamCard extends HTMLElement { private shadow: ShadowRoot constructor() { super() this.shadow = this.attachShadow({ mode: 'open' }) } connectedCallback() { const template = document.getElementById('card-template') as HTMLTemplateElement const clone = template.content.cloneNode(true) as DocumentFragment const avatar = clone.querySelector('.card__avatar') as HTMLImageElement avatar.src = this.getAttribute('avatar') || '/placeholder.png' avatar.alt = this.getAttribute('name') || 'Photo' this.shadow.appendChild(clone) } } customElements.define('team-card', TeamCard) Usage:
<team-card avatar="/team/anna.jpg"> <strong slot="name">Anna Kovaleva</strong> <span slot="role">Lead Frontend Engineer</span> Specializes in React architecture and WebGL visualizations. </team-card> Inline Template for Reusable Components
For components where the template does not need to live in the HTML document, we define it programmatically. Creating the template once at class definition time and cloning it in the constructor is faster than generating innerHTML on every instantiation.
const template = document.createElement('template') template.innerHTML = ` <style> :host { display: inline-flex; align-items: center; gap: 8px; } .badge { padding: 4px 10px; border-radius: 100px; font-size: 12px; font-weight: 600; } :host([color="green"]) .badge { background: #d4edda; color: #155724; } :host([color="red"]) .badge { background: #f8d7da; color: #721c24; } :host([color="blue"]) .badge { background: #d1ecf1; color: #0c5460; } </style> <span class="badge"><slot></slot></span> ` class StatusBadge extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).appendChild(template.content.cloneNode(true)) } } customElements.define('status-badge', StatusBadge) Slot Change Events and Programmatic Slot Access
Web Components can react when the content projected into a slot changes. This is useful for components that display counts or summaries of their slotted items.
connectedCallback() { const defaultSlot = this.shadow.querySelector('slot:not([name])')! defaultSlot.addEventListener('slotchange', () => { const items = (defaultSlot as HTMLSlotElement).assignedElements() this.countEl.textContent = `${items.length} items` }) } We also build components that query slot content programmatically to toggle sections based on whether a named slot has been filled by the consumer.
How We Deliver Web Components
Our process keeps component scope clear and delivery predictable.
- Review your component requirements and define the public API: attributes, slots, and events
- Design the Shadow DOM structure and template markup
- Implement the TypeScript class with lifecycle callbacks and slot handling
- Write automated tests for rendering, attribute changes, and slot projection
- Document usage examples for your development team
| Scope | Timeline |
|---|---|
| 1–2 components with templates and slots | 1–2 working days |
| 3–5 component system with documentation | 3–5 working days |
| Full library of 8+ components with unit tests | 1–2 weeks |
Contact us to discuss your component requirements. We will review your existing UI, define the component API, and provide a quote for the implementation.
How We Deliver
We start with a component inventory. We identify which elements make sense as Web Components. We define the public API for each. We build one component first. You review it. We apply feedback. We build the remaining components. We write automated tests. We deliver documentation with usage examples.







