Composing Components
Nesting custom elements
Section titled “Nesting custom elements”The simplest form of composition is nesting: an outer custom element creates one or more inner custom elements inside connectedCallback. Define the inner elements first so the registry knows about them before the outer element tries to use them.
// Inner element — defined firstclass InnerWidget extends HTMLElement { connectedCallback() { this.textContent = 'I am the inner widget'; }}customElements.define('inner-widget', InnerWidget);
// Outer element — creates the inner one programmaticallyclass OuterWidget extends HTMLElement { connectedCallback() { var inner = document.createElement('inner-widget'); this.appendChild(inner); }}customElements.define('outer-widget', OuterWidget);Place the outer element in HTML and the browser handles the rest:
<outer-widget></outer-widget>Communicating via attributes
Section titled “Communicating via attributes”The most straightforward way for a parent element to pass data to a child is through HTML attributes. The parent calls setAttribute on the child element it creates; the child reads the value with getAttribute in its own connectedCallback.
// Parent sets the child's attributevar child = document.createElement('name-tag');child.setAttribute('name', this.getAttribute('name') || 'friend');this.appendChild(child);
// Child reads itNameTag.prototype.connectedCallback = function() { var name = this.getAttribute('name') || 'stranger'; this.textContent = name;};Attributes are always strings, so convert to the type you need (for example, parseInt for numbers).
Communicating via events
Section titled “Communicating via events”When a child element needs to tell its parent that something happened — a button was clicked, a value was selected — it dispatches a CustomEvent. The parent listens for that event in its own connectedCallback.
Two options on the event constructor matter here:
bubbles: true— the event travels up the DOM tree past the childcomposed: true— the event crosses shadow-DOM boundaries
// Child dispatches an eventthis.addEventListener('click', function() { self.dispatchEvent(new CustomEvent('item-selected', { bubbles: true, composed: true, detail: { value: self.getAttribute('value') } }));});
// Parent listens in connectedCallbackOuterWidget.prototype.connectedCallback = function() { var self = this; // ... build children ... self.addEventListener('item-selected', function(e) { console.log('selected:', e.detail.value); });};Because bubbles: true is set, the parent can attach a single listener to itself and receive events from any child nested inside it — no direct reference needed.
Runnable demo
Section titled “Runnable demo”The demo below composes a <star-rating> from five <rating-star> elements. Each star listens for a click and dispatches a star-select CustomEvent. The outer <star-rating> listens for those events and updates a display span with the chosen rating.