Keith

Custom Tooltip Web Component

by Keith Rowles • 28/10/2023JavaScript

Pink Blue Peach colour gradient

Summary

Created a custom tooltip web component using vanilla javascript, shadow dom, shadowroot, templates and slots.

HTML

Add the html.

<div class="container">
  <p>
    <custom-tooltip text="This is a custom tooltip web component!">
      <span class="highlight">Tooltip Web Component</span>
    </custom-tooltip>
  </p>
</div>

CSS

Add some css to the html document.

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-family: Arial, Helvetica, sans-serif;
}

:root {
  --color-primary: lightblue;
  --color-background: aliceblue;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100vh;
  background-color: var(--color-background);
  margin: 0;
  padding: 0;
}

.highlight {
  background-color: yellow;
}

JavaScript

Add the custom javascript component.

class Tooltip extends HTMLElement {
  constructor() {
    super();
    // this._tooltipContainer;
    this._tooltipIcon;
    this._tooltipVisible = false;
    this._tooltipText = 'No Tool Tip Has Been Set';
    this.attachShadow({
      mode: 'open',
    });
    this.shadowRoot.innerHTML = `
    <style>
        div {
            background-color: black;
            color: white;
            position: absolute;
            z-index: 10;
            padding: 0.7rem;
            border-radius: 5px;
            top: 2rem;
            left: 1rem;
            font-weight: normal;
            line-height: 150%;
        }
        ::slotted(.highlight) {
          border-bottom: 1px dotted red;
        }
        .icon {
          background: black;
          color: white;
          padding: 0.5rem;
          border-radius: 50%
        }
        :host {
          position: relative;
        }
        :host-context(p) {
          font-weight: bold;
        }
    </style>
    <slot>Default Text</slot>
    <span class="icon"> (?)</span>
    `;
  }

  connectedCallback() {
    if (this.hasAttribute('text')) {
      this._tooltipText = this.getAttribute('text');
    }

    this._tooltipIcon = this.shadowRoot.querySelector('span');

    this._tooltipIcon.addEventListener(
      'mouseenter',
      this._showTooltip.bind(this)
    );
    this._tooltipIcon.addEventListener(
      'mouseleave',
      this._hideTooltip.bind(this)
    );

    this._render();
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue === newValue) {
      return;
    }
    if (name === 'text') {
      this._tooltipText = newValue;
    }
  }

  static get observedAttributes() {
    return ['text'];
  }

  disconnectedCallback() {
    // these are technically redundant because event listeners are done by browser - just here for demo if you need clean up
    this._tooltipIcon.removeEventListener('mouseenter', this._showTooltip);
    this._tooltipIcon.removeEventListener('mouseleave', this._hideTooltip);
  }

  _render() {
    let tooltipContainer = this.shadowRoot.querySelector('div');
    if (this._tooltipVisible) {
      tooltipContainer = document.createElement('div');
      tooltipContainer.textContent = this._tooltipText;
      this.shadowRoot.appendChild(tooltipContainer);
    } else {
      if (tooltipContainer) {
        this.shadowRoot.removeChild(tooltipContainer);
      }
    }
  }

  _showTooltip() {
    this._tooltipVisible = true;
    this._render();
  }

  _hideTooltip() {
    this._tooltipVisible = false;
    this._render();
  }
}

customElements.define('custom-tooltip', Tooltip);

Demo

Open demo on JS Fiddle.

Link to Demo