Skip to main content Accessibility Feedback

How to get different Web Components to talk to each other (part 1)

This morning, I had multiple people ask me how you can nest Web Components inside each other and share information from one to another. Today, we’re going to look at how to do exactly that.

Let’s dig in!

🤫 Shhh! I’m working on a new workshop-style course on Web Components. I don’t even have a landing page for it yet, but if you buy my All-Access Pass, you’ll get instant access to it as soon as its ready.

A silly Web Component

For this article, I’ve got a relatively pointless Web Component I’ve built, <wc-highlight>.

<wc-highlight>
	<div>1</div>
	<div>2</div>
	<div>3</div>
</wc-highlight>

When the Web Component instantiates, it…

  1. Gets all of the direct .children elements inside itself.
  2. Sets the first item in that list (with an index of 0) as the active one.
  3. Highlights the first item in the collection by setting [aria-selected="true"].
customElements.define('wc-highlight', class extends HTMLElement {

	/**
	 * Instantiate the custom element
	 */
	constructor () {

		// Always call super first in constructor
		super();

		// Define properties
		this.boxes = Array.from(this.children);
		this.active = 0;

		// Set [aria-selected]
		// If the first element, set to true
		// Otherwise, set to false
		this.boxes.forEach(function (box, index) {
			box.setAttribute('aria-selected', index === 0 ? true : false);
		});

	}

});

I also have a .next() method in the Web Component.

It increases this.active by 1 (and resets it to 0 if you’re at the end of the list). Then, it updates the [aria-selected] attribute to highlight the new item.

/**
 * Skip to the next item
 */
next () {

	// Cache the currently active index
	let current = this.active;

	// Update active index
	this.active++;

	// If at the end, loop back to the start
	if (this.active === this.boxes.length) {
		this.active = 0;
	}

	// Update [aria-selected]
	this.boxes[current].setAttribute('aria-selected', false);
	this.boxes[this.active].setAttribute('aria-selected', true);

}

By running it on the <wc-highlight> element, you can shift highlighting from the current element to the next one in the list.

let highlight = document.querySelector('wc-highlight');
highlight.next();

Here’s a demo.

Nesting a Web Component inside it

For a Web Component this simple, I’d typically have my controls to move to the next item as part of the component.

But on larger projects, it can be helpful to have a series of smaller-but-interconnected components that each handle one small piece of the UI and talk to each other.

For learning purposes, let’s create a <wc-highlight-controls> custom element inside our <wc-highlight> Web Component constructor(), and inject it into the DOM.

/**
 * Instantiate the custom element
 */
constructor () {

	// ...
	this.boxes.forEach(function (box, index) {
		box.setAttribute('aria-selected', index === 0 ? true : false);
	});

	// Append controls
	let controls = document.createElement('wc-highlight-controls');
	this.append(controls);

}

Then, we can define <wc-highlight-controls> as its own standalone Web Component.

customElements.define('wc-highlight-controls', class extends HTMLElement {

	/**
	 * Instantiate the custom element
	 */
	constructor () {

		// Always call super first in constructor
		super();

		// Do stuff...
	}

});

Inside the constructor(), we can use the Element.closest() method to get the parent <wc-highlight> element, and assign it to this.highlight.

If no parent element is found, we’ll bail early with the return operator.

/**
 * Instantiate the custom element
 */
constructor () {

	// Always call super first in constructor
	super();

	// Get parent <wc-highlight> element
	this.highlight = this.closest('wc-highlight');
	if (!this.highlight) return;

}

Next, we’ll create a button element, give it some text, and .append() it inside our Web Component.

Then, we’ll add a click event listener to it.

/**
 * Instantiate the custom element
 */
constructor () {

	// Always call super first in constructor
	super();

	// Get parent <wc-highlight> element
	this.highlight = this.closest('wc-highlight');
	if (!this.highlight) return;

	// Create button
	this.button = document.createElement('button');
	this.button.textContent = 'Next';
	this.append(this.button);

	// Listen for button clicks
	this.button.addEventListener('click', this);

}

Inside our handleEvent() method, we’ll run the .next() method on this.highlight to jump to the next item.

/**
 * Handle events
 * @param {Event} event The event object
 */
handleEvent (event) {
	this.highlight.next();
}

Here’s another demo.

This is a very manual connection

This method relies on tightly coupled DOM elements.

It works, but it can be a bit fragile because it relies on specific elements loading in the right order and always have access to each other.

Tomorrow, we’re going to look at a method I prefer: Custom Events.

These give you the ability to customize and share data between Web Components in a much more flexible, decoupled way.

They also allow developers to extend the Web Component in ways you might not think of or want to support directly when you create it.