Web Components Polyfills

1. Custom Elements v1 Polyfill Implementation

Feature API Browser Support Polyfill
customElements.define() customElements.define('my-element', MyElement) Modern browsers @webcomponents/custom-elements
Element Class class MyElement extends HTMLElement ES6 class syntax Babel transpilation + polyfill
Lifecycle Callbacks connectedCallback, disconnectedCallback Define behavior MutationObserver for tracking
Attributes observedAttributes, attributeChangedCallback Watch attribute changes Polyfill observes mutations
Autonomous Elements <my-element> Custom tag names Full polyfill support
Customized Built-ins <button is="my-button"> Extends native elements Limited Safari support

Example: Custom Elements polyfill

// Custom Elements v1 polyfill (simplified)
(function() {
    if ('customElements' in window) {
        return; // Native support
    }
    
    var CustomElementRegistry = function() {
        this._definitions = new Map();
        this._whenDefinedPromises = new Map();
        this._upgradeQueue = [];
    };
    
    CustomElementRegistry.prototype.define = function(name, constructor, options) {
        // Validate tag name
        if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(name)) {
            throw new Error('Invalid custom element name: ' + name);
        }
        
        if (this._definitions.has(name)) {
            throw new Error('Element already defined: ' + name);
        }
        
        // Store definition
        this._definitions.set(name, {
            name: name,
            constructor: constructor,
            options: options || {}
        });
        
        // Resolve whenDefined promise
        if (this._whenDefinedPromises.has(name)) {
            this._whenDefinedPromises.get(name).resolve();
            this._whenDefinedPromises.delete(name);
        }
        
        // Upgrade existing elements
        this._upgradeElements(name);
    };
    
    CustomElementRegistry.prototype.get = function(name) {
        var def = this._definitions.get(name);
        return def ? def.constructor : undefined;
    };
    
    CustomElementRegistry.prototype.whenDefined = function(name) {
        if (this._definitions.has(name)) {
            return Promise.resolve();
        }
        
        if (!this._whenDefinedPromises.has(name)) {
            var resolve, reject;
            var promise = new Promise(function(res, rej) {
                resolve = res;
                reject = rej;
            });
            promise.resolve = resolve;
            promise.reject = reject;
            this._whenDefinedPromises.set(name, promise);
        }
        
        return this._whenDefinedPromises.get(name);
    };
    
    CustomElementRegistry.prototype._upgradeElements = function(name) {
        var elements = document.getElementsByTagName(name);
        
        for (var i = 0; i < elements.length; i++) {
            this._upgradeElement(elements[i], name);
        }
    };
    
    CustomElementRegistry.prototype._upgradeElement = function(element, name) {
        if (element.__upgraded) {
            return;
        }
        
        var definition = this._definitions.get(name);
        if (!definition) {
            return;
        }
        
        // Create instance
        var instance = new definition.constructor();
        
        // Copy attributes
        for (var i = 0; i < element.attributes.length; i++) {
            var attr = element.attributes[i];
            instance.setAttribute(attr.name, attr.value);
        }
        
        // Copy children
        while (element.firstChild) {
            instance.appendChild(element.firstChild);
        }
        
        // Replace element
        element.parentNode.replaceChild(instance, element);
        
        // Mark as upgraded
        instance.__upgraded = true;
        
        // Call connectedCallback if present
        if (instance.connectedCallback) {
            instance.connectedCallback();
        }
    };
    
    CustomElementRegistry.prototype._observeNewElements = function() {
        var self = this;
        
        if (!window.MutationObserver) {
            return;
        }
        
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                mutation.addedNodes.forEach(function(node) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        var name = node.tagName.toLowerCase();
                        if (self._definitions.has(name)) {
                            self._upgradeElement(node, name);
                        }
                    }
                });
            });
        });
        
        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    };
    
    // Create global registry
    var registry = new CustomElementRegistry();
    registry._observeNewElements();
    
    Object.defineProperty(window, 'customElements', {
        value: registry,
        writable: false,
        configurable: true
    });
    
    // Polyfill HTMLElement if needed
    if (typeof HTMLElement !== 'function') {
        window.HTMLElement = function() {};
    }
})();

// Usage example
class MyButton extends HTMLElement {
    constructor() {
        super();
        this.addEventListener('click', this._handleClick.bind(this));
    }
    
    connectedCallback() {
        this.innerHTML = '<button>' + (this.getAttribute('label') || 'Click me') + '</button>';
    }
    
    disconnectedCallback() {
        // Cleanup
    }
    
    static get observedAttributes() {
        return ['label'];
    }
    
    attributeChangedCallback(name, oldValue, newValue) {
        if (name === 'label') {
            this.querySelector('button').textContent = newValue;
        }
    }
    
    _handleClick() {
        this.dispatchEvent(new CustomEvent('my-click', { detail: { message: 'Clicked!' } }));
    }
}

customElements.define('my-button', MyButton);
Note: Use @webcomponents/webcomponentsjs polyfill package for production. It includes Custom Elements, Shadow DOM, and HTML Templates polyfills.

2. Shadow DOM v1 Polyfill and Limitations

Feature API Support Polyfill Limitations
attachShadow() element.attachShadow({ mode: 'open' }) Modern browsers Not true encapsulation
Shadow Root element.shadowRoot Access shadow content Open mode only in polyfill
Style Encapsulation Scoped styles inside shadow Native isolation Scoped class names instead
Slot Elements <slot> for content projection Native support Polyfill redistributes content
:host selector Target host element from shadow Native support Replaced with scoped classes
::slotted() Style slotted content Native support Limited polyfill support

Example: Shadow DOM polyfill (simplified)

// Shadow DOM v1 polyfill (simplified concept)
(function() {
    if ('attachShadow' in Element.prototype) {
        return; // Native support
    }
    
    var ShadowRoot = function(host, mode) {
        this.host = host;
        this.mode = mode;
        this._innerHTML = '';
        this._children = [];
        
        // Create shadow container
        this._container = document.createElement('div');
        this._container.className = '__shadow-root__';
        this._container.style.display = 'contents'; // Pass-through layout
    };
    
    // Implement appendChild, removeChild, etc.
    ShadowRoot.prototype.appendChild = function(node) {
        this._children.push(node);
        this._container.appendChild(node);
        return node;
    };
    
    Object.defineProperty(ShadowRoot.prototype, 'innerHTML', {
        get: function() {
            return this._container.innerHTML;
        },
        set: function(html) {
            this._container.innerHTML = html;
            this._scopeStyles();
            this._processSlots();
        }
    });
    
    ShadowRoot.prototype._scopeStyles = function() {
        var styles = this._container.querySelectorAll('style');
        var scopeId = this.host.getAttribute('data-shadow-id') || 
                     'shadow-' + Math.random().toString(36).substr(2, 9);
        
        this.host.setAttribute('data-shadow-id', scopeId);
        
        for (var i = 0; i < styles.length; i++) {
            var style = styles[i];
            var css = style.textContent;
            
            // Scope selectors
            css = css.replace(/:host\b/g, '[data-shadow-id="' + scopeId + '"]');
            css = css.replace(/([^{}]+)\{/g, function(match, selectors) {
                if (selectors.indexOf('@') === 0) return match; // Skip at-rules
                
                var scoped = selectors.split(',').map(function(sel) {
                    sel = sel.trim();
                    if (sel.indexOf('[data-shadow-id') >= 0) return sel;
                    return '[data-shadow-id="' + scopeId + '"] ' + sel;
                }).join(', ');
                
                return scoped + ' {';
            });
            
            style.textContent = css;
        }
        
        // Add scope to all elements
        var elements = this._container.querySelectorAll('*');
        for (var i = 0; i < elements.length; i++) {
            elements[i].setAttribute('data-shadow-child', scopeId);
        }
    };
    
    ShadowRoot.prototype._processSlots = function() {
        var slots = this._container.querySelectorAll('slot');
        var host = this.host;
        
        for (var i = 0; i < slots.length; i++) {
            var slot = slots[i];
            var slotName = slot.getAttribute('name') || '';
            
            // Find content for this slot
            var content;
            if (slotName) {
                content = host.querySelectorAll('[slot="' + slotName + '"]');
            } else {
                // Default slot gets all children without slot attribute
                content = Array.prototype.filter.call(host.childNodes, function(node) {
                    return !node.getAttribute || !node.getAttribute('slot');
                });
            }
            
            // Move content into slot
            while (slot.firstChild) {
                slot.removeChild(slot.firstChild);
            }
            
            for (var j = 0; j < content.length; j++) {
                slot.appendChild(content[j].cloneNode(true));
            }
        }
    };
    
    // Polyfill attachShadow
    Element.prototype.attachShadow = function(options) {
        if (this.__shadowRoot) {
            throw new Error('Shadow root already attached');
        }
        
        var mode = options && options.mode || 'open';
        var shadowRoot = new ShadowRoot(this, mode);
        
        this.__shadowRoot = shadowRoot;
        
        // Clear host content and append shadow container
        while (this.firstChild) {
            shadowRoot._originalContent = shadowRoot._originalContent || [];
            shadowRoot._originalContent.push(this.firstChild);
            this.removeChild(this.firstChild);
        }
        
        this.appendChild(shadowRoot._container);
        
        return shadowRoot;
    };
    
    // Polyfill shadowRoot getter
    Object.defineProperty(Element.prototype, 'shadowRoot', {
        get: function() {
            return this.__shadowRoot && this.__shadowRoot.mode === 'open' 
                   ? this.__shadowRoot 
                   : null;
        }
    });
})();

// Usage example
class ShadowCard extends HTMLElement {
    constructor() {
        super();
        
        var shadow = this.attachShadow({ mode: 'open' });
        
        shadow.innerHTML = `
            <style>
                :host {
                    display: block;
                    border: 1px solid #ccc;
                    border-radius: 4px;
                    padding: 16px;
                }
                
                .header {
                    font-weight: bold;
                    margin-bottom: 8px;
                }
                
                .content {
                    color: #666;
                }
            </style>
            
            <div class="header">
                <slot name="header">Default Header</slot>
            </div>
            
            <div class="content">
                <slot>Default Content</slot>
            </div>
        `;
    }
}

customElements.define('shadow-card', ShadowCard);
Warning: Shadow DOM polyfills cannot provide true style encapsulation. Global styles may still leak in. Consider using CSS Modules or scoped CSS instead for better polyfill support.

3. HTML Templates and Template Element Polyfills

Feature API Browser Support Polyfill Method
<template> Inert content container IE not supported Hidden div fallback
content property template.content DocumentFragment Create fragment manually
cloneNode() template.content.cloneNode(true) Clone template content Standard cloning works
Inert Scripts Scripts don't execute in template Native behavior Remove type or src attributes

Example: HTML Template polyfill

// HTML Template element polyfill
(function() {
    if ('content' in document.createElement('template')) {
        return; // Native support
    }
    
    // Polyfill template element
    var templates = document.getElementsByTagName('template');
    
    for (var i = 0; i < templates.length; i++) {
        var template = templates[i];
        
        // Create DocumentFragment for content
        var content = document.createDocumentFragment();
        var child;
        
        while (child = template.firstChild) {
            content.appendChild(child);
        }
        
        // Store content as property
        Object.defineProperty(template, 'content', {
            get: function() {
                return this._content || (this._content = content);
            }
        });
        
        // Hide template
        template.style.display = 'none';
    }
    
    // Observe new templates
    if (window.MutationObserver) {
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                mutation.addedNodes.forEach(function(node) {
                    if (node.tagName === 'TEMPLATE') {
                        initTemplate(node);
                    }
                });
            });
        });
        
        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }
    
    function initTemplate(template) {
        if (template.content) return;
        
        var content = document.createDocumentFragment();
        var child;
        
        while (child = template.firstChild) {
            content.appendChild(child);
        }
        
        Object.defineProperty(template, 'content', {
            get: function() {
                return this._content || (this._content = content);
            }
        });
        
        template.style.display = 'none';
    }
})();

// Usage example
var template = document.getElementById('my-template');
var clone = document.importNode(template.content, true);
document.body.appendChild(clone);

// HTML:
// <template id="my-template">
//   <div class="card">
//     <h3>Title</h3>
//     <p>Content</p>
//   </div>
// </template>
Note: The <template> element is well-supported in modern browsers. For IE, use the polyfill or fallback to hidden <div> containers.

4. Slot Element and Content Projection

Feature Syntax Description Polyfill
Default Slot <slot></slot> Unnamed slot for default content Content redistribution
Named Slot <slot name="header"></slot> Target specific content Match by slot attribute
Slotted Content <div slot="header"> Assign content to slot Move/clone content
Fallback Content Content inside <slot> Shown if no slotted content Keep if no match found
assignedNodes() slot.assignedNodes() Get slotted nodes Track assigned content
slotchange event Fires when slot content changes React to content updates MutationObserver

Example: Slot element polyfill

// Slot element polyfill (simplified)
var SlotPolyfill = {
    processSlots: function(shadowRoot, hostElement) {
        var slots = shadowRoot.querySelectorAll('slot');
        
        for (var i = 0; i < slots.length; i++) {
            this.processSlot(slots[i], hostElement);
        }
    },
    
    processSlot: function(slot, hostElement) {
        var slotName = slot.getAttribute('name');
        var assignedNodes = [];
        
        if (slotName) {
            // Named slot - find matching content
            var matches = hostElement.querySelectorAll('[slot="' + slotName + '"]');
            assignedNodes = Array.prototype.slice.call(matches);
        } else {
            // Default slot - find unassigned content
            assignedNodes = Array.prototype.filter.call(hostElement.childNodes, function(node) {
                if (node.nodeType === Node.TEXT_NODE) {
                    return node.textContent.trim() !== '';
                }
                return !node.getAttribute || !node.getAttribute('slot');
            });
        }
        
        // Store assigned nodes for API
        slot._assignedNodes = assignedNodes;
        
        // Clear slot
        while (slot.firstChild) {
            slot.removeChild(slot.firstChild);
        }
        
        // Insert assigned content or fallback
        if (assignedNodes.length > 0) {
            assignedNodes.forEach(function(node) {
                slot.appendChild(node.cloneNode(true));
            });
        }
        // If no assigned nodes, slot keeps its fallback content
    },
    
    // Implement assignedNodes() method
    polyfillAssignedNodes: function() {
        if (!('assignedNodes' in HTMLSlotElement.prototype)) {
            HTMLSlotElement.prototype.assignedNodes = function(options) {
                return this._assignedNodes || [];
            };
        }
        
        if (!('assignedElements' in HTMLSlotElement.prototype)) {
            HTMLSlotElement.prototype.assignedElements = function(options) {
                var nodes = this._assignedNodes || [];
                return nodes.filter(function(node) {
                    return node.nodeType === Node.ELEMENT_NODE;
                });
            };
        }
    },
    
    watchSlotChanges: function(slot, hostElement) {
        if (!window.MutationObserver) return;
        
        var self = this;
        var observer = new MutationObserver(function(mutations) {
            self.processSlot(slot, hostElement);
            
            // Dispatch slotchange event
            var event = document.createEvent('Event');
            event.initEvent('slotchange', true, false);
            slot.dispatchEvent(event);
        });
        
        observer.observe(hostElement, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['slot']
        });
    }
};

// Usage in custom element
class SlottedCard extends HTMLElement {
    constructor() {
        super();
        
        var shadow = this.attachShadow({ mode: 'open' });
        
        shadow.innerHTML = `
            <style>
                .card {
                    border: 1px solid #ccc;
                    padding: 16px;
                }
                
                .header {
                    font-size: 18px;
                    font-weight: bold;
                    margin-bottom: 8px;
                }
                
                .footer {
                    margin-top: 8px;
                    font-size: 12px;
                    color: #666;
                }
            </style>
            
            <div class="card">
                <div class="header">
                    <slot name="header">Default Header</slot>
                </div>
                
                <div class="content">
                    <slot>Default content goes here</slot>
                </div>
                
                <div class="footer">
                    <slot name="footer">Default Footer</slot>
                </div>
            </div>
        `;
        
        // Process slots with polyfill if needed
        if (!('assignedNodes' in HTMLSlotElement.prototype)) {
            SlotPolyfill.processSlots(shadow, this);
        }
    }
}

customElements.define('slotted-card', SlottedCard);

// HTML usage:
// <slotted-card>
//   <h2 slot="header">My Title</h2>
//   <p>This is my content</p>
//   <span slot="footer">Copyright 2025</span>
// </slotted-card>
Note: Slot polyfills work by cloning content, not moving it. Event listeners on original content won't work. Re-attach listeners after slotting.

5. HTML Imports Alternative Implementations

Technology Status Alternative Use Case
HTML Imports DEPRECATED ES Modules Component loading
<link rel="import"> Never standardized fetch() + innerHTML Load HTML fragments
ES Modules Modern browsers <script type="module"> Preferred method
Dynamic import() Modern browsers import('./component.js') Lazy loading
Template Includes No native support Build tools / SSR Server-side composition

Example: HTML Imports alternative with fetch

// HTML Imports alternative using fetch
var HTMLLoader = {
    cache: new Map(),
    
    import: function(url) {
        // Return cached promise if exists
        if (this.cache.has(url)) {
            return this.cache.get(url);
        }
        
        // Fetch and parse HTML
        var promise = fetch(url)
            .then(function(response) {
                if (!response.ok) {
                    throw new Error('Failed to load: ' + url);
                }
                return response.text();
            })
            .then(function(html) {
                // Parse HTML into document fragment
                var template = document.createElement('template');
                template.innerHTML = html;
                
                return {
                    url: url,
                    content: template.content,
                    querySelector: function(selector) {
                        return template.content.querySelector(selector);
                    },
                    querySelectorAll: function(selector) {
                        return template.content.querySelectorAll(selector);
                    }
                };
            });
        
        this.cache.set(url, promise);
        return promise;
    },
    
    importAll: function(urls) {
        return Promise.all(urls.map(this.import.bind(this)));
    }
};

// Usage
HTMLLoader.import('/components/card.html')
    .then(function(imported) {
        // Get template from imported document
        var template = imported.querySelector('template#card-template');
        var clone = document.importNode(template.content, true);
        
        document.body.appendChild(clone);
    })
    .catch(function(error) {
        console.error('Import failed:', error);
    });

// Load multiple components
HTMLLoader.importAll([
    '/components/header.html',
    '/components/footer.html',
    '/components/sidebar.html'
]).then(function(imports) {
    console.log('All components loaded');
});

Example: ES Module approach for Web Components

// Modern approach: ES Modules for Web Components
// component.js
export class MyComponent extends HTMLElement {
    constructor() {
        super();
        
        var shadow = this.attachShadow({ mode: 'open' });
        shadow.innerHTML = this.getTemplate();
    }
    
    getTemplate() {
        return `
            <style>
                :host {
                    display: block;
                    padding: 16px;
                }
            </style>
            
            <div class="component">
                <slot></slot>
            </div>
        `;
    }
    
    connectedCallback() {
        console.log('Component connected');
    }
}

// Auto-register when module loads
customElements.define('my-component', MyComponent);

// main.js - Import and use
import { MyComponent } from './component.js';

// Component is auto-registered, just use it
document.body.innerHTML = '<my-component>Hello!</my-component>';

// Or lazy load
async function loadComponent() {
    const module = await import('./component.js');
    console.log('Component loaded and registered');
}

// Load on demand
document.getElementById('load-btn').addEventListener('click', loadComponent);
Warning: HTML Imports are deprecated and removed from browsers. Use ES Modules with import statements or dynamic import() for component loading.

6. Scoped CSS and Style Encapsulation

Method Approach Browser Support Pros/Cons
Shadow DOM Native style encapsulation Modern browsers True isolation, polyfill limitations
Scoped Attribute <style scoped> REMOVED FROM SPEC Never fully supported
CSS Modules Build-time scoping All browsers Requires build step
BEM Naming Naming convention All browsers Manual, no true isolation
CSS-in-JS JavaScript-generated styles All browsers Runtime overhead
Data Attributes Scope with unique attributes All browsers Simple, no build tools

Example: Scoped CSS polyfill with data attributes

// Scoped CSS polyfill
var ScopedCSS = {
    scopeId: 0,
    
    applyScoped: function(container) {
        var scopeId = 'scope-' + (++this.scopeId);
        
        // Add scope attribute to container
        container.setAttribute('data-scope', scopeId);
        
        // Find style elements
        var styles = container.querySelectorAll('style[scoped]');
        
        for (var i = 0; i < styles.length; i++) {
            this.scopeStyle(styles[i], scopeId);
        }
        
        // Add scope to all descendants
        var elements = container.querySelectorAll('*');
        for (var i = 0; i < elements.length; i++) {
            if (elements[i].tagName !== 'STYLE') {
                elements[i].setAttribute('data-scope-child', scopeId);
            }
        }
    },
    
    scopeStyle: function(styleElement, scopeId) {
        var css = styleElement.textContent;
        
        // Scope all selectors
        css = css.replace(/([^{}]+)\{/g, function(match, selectors) {
            // Skip at-rules
            if (selectors.trim().indexOf('@') === 0) {
                return match;
            }
            
            var scoped = selectors.split(',').map(function(selector) {
                selector = selector.trim();
                
                // Scope the selector
                return '[data-scope="' + scopeId + '"] ' + selector;
            }).join(', ');
            
            return scoped + ' {';
        });
        
        styleElement.textContent = css;
        styleElement.removeAttribute('scoped');
        
        // Move style to head for better performance
        if (styleElement.parentNode !== document.head) {
            document.head.appendChild(styleElement);
        }
    },
    
    // Auto-process on DOM ready
    init: function() {
        var containers = document.querySelectorAll('[data-scoped-css]');
        
        for (var i = 0; i < containers.length; i++) {
            this.applyScoped(containers[i]);
        }
        
        // Watch for new containers
        this.observeDOM();
    },
    
    observeDOM: function() {
        if (!window.MutationObserver) return;
        
        var self = this;
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                mutation.addedNodes.forEach(function(node) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        if (node.hasAttribute && node.hasAttribute('data-scoped-css')) {
                            self.applyScoped(node);
                        }
                    }
                });
            });
        });
        
        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }
};

// Initialize
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', function() {
        ScopedCSS.init();
    });
} else {
    ScopedCSS.init();
}

// Usage:
// <div data-scoped-css>
//   <style scoped>
//     .title { color: blue; }
//     p { font-size: 14px; }
//   </style>
//   
//   <h1 class="title">Scoped Title</h1>
//   <p>Scoped paragraph</p>
// </div>

Example: CSS-in-JS approach for components

// Simple CSS-in-JS for Web Components
class StyledComponent extends HTMLElement {
    constructor() {
        super();
        
        this.scopeId = 'component-' + Math.random().toString(36).substr(2, 9);
        this.setAttribute('data-component-id', this.scopeId);
        
        this.injectStyles();
        this.render();
    }
    
    getStyles() {
        return {
            '.container': {
                padding: '16px',
                border: '1px solid #ccc',
                borderRadius: '4px'
            },
            '.title': {
                fontSize: '18px',
                fontWeight: 'bold',
                marginBottom: '8px'
            },
            '.content': {
                color: '#666'
            }
        };
    }
    
    injectStyles() {
        var styles = this.getStyles();
        var css = '';
        
        // Convert styles object to CSS string
        for (var selector in styles) {
            var scopedSelector = '[data-component-id="' + this.scopeId + '"] ' + selector;
            css += scopedSelector + ' {';
            
            var rules = styles[selector];
            for (var prop in rules) {
                var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
                css += cssProp + ': ' + rules[prop] + ';';
            }
            
            css += '}';
        }
        
        // Create and inject style element
        var styleEl = document.createElement('style');
        styleEl.textContent = css;
        document.head.appendChild(styleEl);
        
        // Store reference for cleanup
        this._styleElement = styleEl;
    }
    
    render() {
        this.innerHTML = `
            <div class="container">
                <div class="title">${this.getAttribute('title') || 'Title'}</div>
                <div class="content">
                    <slot></slot>
                </div>
            </div>
        `;
    }
    
    disconnectedCallback() {
        // Cleanup styles when component removed
        if (this._styleElement && this._styleElement.parentNode) {
            this._styleElement.parentNode.removeChild(this._styleElement);
        }
    }
}

customElements.define('styled-component', StyledComponent);

// Usage:
// <styled-component title="My Component">
//   This is the content
// </styled-component>

Key Takeaways - Web Components

  • Custom Elements: Use @webcomponents/custom-elements polyfill for IE11
  • Shadow DOM: Polyfills lack true encapsulation, consider CSS Modules
  • Templates: Well-supported, simple polyfill for IE using DocumentFragment
  • Slots: Polyfill clones content instead of moving it, re-attach listeners
  • HTML Imports: Deprecated - use ES Modules with import()
  • Scoped CSS: Use data attributes or CSS-in-JS for reliable scoping