How to communicate between Web Components (native UI)?

If you look at Web Components as being like built in components like <div> and <audio> then you can answer your own question. The components do not talk to each other.

Once you start allowing components to talk directly to each other then you don't really have components you have a system that is tied together and you can not use Component A without Component B. This is tied too tightly together.

Instead, inside the parent code that owns the two components, you add code that allows you to receive events from component A and call functions or set parameters in Component B, and the other way around.

Having said that there are two exceptions to this rule with built in components:

  1. The <label> tag: It uses the for attribute to take in an ID of another component and, if set and valid, then it passes focus on to the other component when you click on the <label>

  2. The <form> tag: This looks for form elements that are children to gather the data needed to post the form.

But both of these are still not TIED to anything. The <label> is told the recipient of the focus event and only passes it along if the ID is set and valid or to the first form element as a child. And the <form> element does not care what child elements exist or how many it just goes through all of its descendants finding elements that are form elements and grabs their value property.

But as a general rule you should avoid having one sibling component talk directly to another sibling. The methods of cross communications in the two examples above are probably the only exceptions.

Instead your parent code should listen for events and call functions or set properties.

Yes, you can wrap that functionality in an new, parent, component, but please save yourself a ton of grief and avoid spaghetti code.

As a general rule I never allow siblings elements to talk to each other and the only way they can talk to their parents is through events. Parents can talk directly to their children through attributes, properties and functions. But it should be avoided in all other conditions.


Working example

In your parent code (html/css) you should subscribe to events emited by <chat-form> and send event data to <chat-history> by execute its methods (add in below example)

// WEB COMPONENT 1: chat-form
customElements.define('chat-form', class extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `Form<br><input id="msg" value="abc"/>
      <button id="btn">send</button>`;

    btn.onclick = () => {
      // alternative to below code
      // use this.onsend() or non recommended eval(this.getAttribute('onsend'))
      this.dispatchEvent(new CustomEvent('send',{detail: {message: msg.value} }))
      msg.value = '';
    }
  }
})


// WEB COMPONENT 2: chat-history
customElements.define('chat-history', class extends HTMLElement {
  add(msg) {
    let s = ""
    this.messages = [...(this.messages || []), msg];
    for (let m of this.messages) s += `<li>${m}</li>`
    this.innerHTML = `<div><br>History<ul>${s}</ul></div>`
  }
})


// -----------------
// PARENT CODE 
// (e.g. in index.html which use above two WebComponents)
// Parent must just subscribe chat-form send event, and when
// receive message then it shoud give it to chat-history add method
// -----------------

myChatForm.addEventListener('send', e => {
  myChatHistory.add(e.detail.message)
});
body {background: white}
<h3>Hello!</h3>

<chat-form id="myChatForm"></chat-form>

<div>Type something</div>

<chat-history id="myChatHistory"></chat-history>