Web Components

Posted Friday, January 17, 2025 by Sri. Tagged MEMO, CHEATSHEET
EDITING PHASE:gathering info...

Web Components define a way for browser-based web developers to create reusable components natively will encapsulation of the details away from the main DOM. The spec became fully supported by all major browsers at the end of 2020.

Key Concepts

  • A web component is an extension of HTMLElement
  • The global customElements object is used to map a hyphenated tagname to the component.
  • Web components can declare a lightweight shadow DOM that hides the implementation of the component from the rest of the DOM, avoiding collisions and maintaining performance.
  • Web components can have slots that are occupied by child elements.
  • Web components can use new CSS selectors :host and :slotted() to internally style the component areas.

Example of a hypothetical Web Componenent in HTML.

<doc-section>
<div slot='header'> ... </div>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</doc-section>

This component declare two sources of content or "slots"

  • a "header" slot into which the div with be appended
  • a implied "default" slot which the paragraphs will be appended

The implementation of the web component can add decorative styling and container divs that would otherwise clutter up the declaration. Since slots are HTMLElements, they are roughly comparable to React child components, and the HTMLElement attributes are similar to React props passing. You can use vanilla JS to access and manipulate them.

Boilerplate Declaration

Here is component.js which declares the skeleton for a custom component.

/// components.js ///

class MyCustomElement extends HTMLElement {
constructor() {
super(); // Always call super first in the constructor
}
connectedCallback() {} // element is inserted into the DOM
disconnectedCallback() {} // element is removed from the DOM
adoptedCallback() {} // element is moved to a new document
attributeChangedCallback(name, oldValue, newValue) {} // attribute is added, removed, or changed
static get observedAttributes(): string[] {} // Replace with actual attribute names to observe
}

// define the custom element
customElements.define('my-custom-element', MyCustomElement);

To use the component in an HTML file, use <script src='components.js' defer> in the <head> to declare customElements. This will minimize the "flash of styled content". The defer flag makes the script load async, and executed after HTML is fully parsed before rendering.

<!DOCTYPE html>
<html lang="en">
<head>
<script src="js/bundle.js" defer></script>
</head>
<body>

<my-custom-element></my-custom-element>

</body>
</html>

Defining a Component

Here's a dropdown element that makes use of the shadowRoot element that encapsulates everything inside the component, which is a lightweight DOM element that just contains child nodes. It's an extension of DocumentFragment, itself a lightweigt version of Document.

class DropdownElement extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host { display: inline-block; position: relative; }
.dropdown { display: none; position: absolute; background: white; border: 1px solid #ccc; }
.dropdown.open { display: block; }
button { cursor: pointer; }
</style>
<button id="toggle">Toggle</button>
<div class="dropdown" id="menu">
<slot></slot>
</div>
`
;

this.toggleButton = shadow.querySelector('#toggle');
this.menu = shadow.querySelector('#menu');

this.toggleButton.addEventListener('click', () => {
this.menu.classList.toggle('open');
});
}
}

customElements.define('dropdown-element', DropdownElement);

Usage looks like this:

<dropdown-element>
<ul>
<li>Option 1</li>
<li>Option 2</li>
<li>Option 3</li>
</ul>
</dropdown-element>

Handling Children with Slots

Web Components use a special tag <slot> when defining its innerHTML property.

  • <slot name='keyname'> is a named slot
  • <slot> without the keyname is a default slot into which everything else is added.

When using a Web Component in HTML, the companion attribute slot='keyname' is used to designate in which slot content will be appended. If content without the slot attribute is used and there is no default slot, then the children is not rendered.

Styling Components

There are two special CSS selectors available for styling:

:host

This pseudo-class selector targets the ShadowRoot. You can style it by adding <style> declaration to the innerHTML property of the shadowRoot, or creating a style object and appending it using shadowRoot.appendChild().

note: a pseudo-class targets elements based on their state or relationship to other elements.

::slotted(element)

This function-like pseudo-element selector will target slots defined in the shadowRoot. It will not target anything that isn't in a <slot> declaration. It's intended to be used for styling the children included in the HTML declaration; you can style your non-slot components, or use anything defined in the global DOM scope.

It's limited to the following simple selectors:

  • element - e.g. div, span
  • class - e.g. .highlight
  • attribute - e.g. [data-active='true'], but not div[class='foo'] The reason for the limits is for performance. Web Components are using lightweight versions of the DOM.

note: a pseudo-element targets a part of an element, like p::before { content:'Prefix: ' }. However, the ::slotted() pseudo-element uses special syntax; you can not do compound selection like div::slotted(.fooClass).


References

Here is a list of all the ShadowRoot methods and properties, including inherited ones from DocumentFragment. You'll see that properties like innerHTML are explicitly defined; ShadowRoot is not an HTMLElement.

ShadowRoot Properties

  • host: (readonly) The element to which the shadow root is attached.
  • mode: (readonly) Indicates if the shadow root is "open" or "closed".
  • delegatesFocus: (readonly) A boolean indicating if focus delegation is enabled (optional feature).
  • innerHTML: Gets or sets the HTML content of the shadow root.

inherited from DocumentFragment

  • childNodes: (readonly) A NodeList of child nodes.
  • children: (readonly) A HTMLCollection of child elements.

ShadowRoot Methods

  • elementFromPoint(x, y): Returns the topmost element at the given (x, y) coordinates within the shadow DOM.
  • elementsFromPoint(x, y): Returns all elements at the given (x, y) coordinates within the shadow DOM.
  • getSelection(): Returns the Selection object representing the text selection within the shadow DOM.

inherited from DocumentFragment

  • querySelector(selectors): Returns the first element matching the selector within the shadow DOM.
  • querySelectorAll(selectors): Returns a NodeList of all matching elements.
  • appendChild(node): Appends a child node.
  • removeChild(node): Removes a child node.
  • replaceChild(newNode, oldNode): Replaces a child node.
  • cloneNode(deep): Clones the content of the shadow root.
  • hasChildNodes(): Returns true if the shadow root has child nodes.