Web Components Cheatsheet

Posted Friday, January 17, 2025 by Sri. Tagged MEMO, CHEATSHEET

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 (e.g. <my-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 helping performance.
  • Web components can have slots that are occupied by child elements inside the custom tag.
  • Web components can use new CSS selectors :host and :slotted() for internal styling.

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 slots:

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

An implementation of a web component can add decorative styling and extra elements that would otherwise clutter up the declaration by declaring them in the class constructor (an extension of HTMLElement). The <slot> tag is an HTMLElement, and are roughly comparable to React child components. Likewise, any attributes defined in the slot are similar to React props passing. You can use vanilla JS to access and manipulate them.

Boilerplate Declaration

Here is a skeletal component.js showing the overall structure of a custom element.

/// 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 the custom elements that were added through the customElements.define() call.

Note: By using defer, the script will load asynchronously, and be executed after HTML is fully parsed before rendering. This avoids the "flash of unstyled content" that would happen otherwise.

<!DOCTYPE html>
<html lang="en">
<head>
<script src="components.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, defined as shadow in the code example below. The shadow element is a lightweight DOM element (containing only child nodes) that is separate from the main DOM, which avoids namespace collisions.

class DropdownElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.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 to which everything else is appended

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 custom component tag itself. 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. The syntax is very specific.

  • 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.

Furthermore, this selctor is limited to 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: technically speaking, a pseudo-element targets a part of an element, like p::before { content:'Prefix: ' }. However, the ::slotted() does not work like other pseudo-element selectors; 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.