← Back to all snippets
JAVASCRIPT

Building a Custom v-tooltip Directive in Vue 3

Discover how to extend Vue 3's capabilities by creating a custom directive for interactive tooltips, enhancing user experience and adding reusable behavior to any DOM element.

// 1. Define the custom directive (e.g., in directives/vTooltip.js)
import { createApp, h } from 'vue';

const tooltipAppMap = new WeakMap();

const vTooltip = {
  mounted(el, binding) {
    const text = binding.value || el.getAttribute('title'); // Use v-tooltip value or existing title
    if (!text) return;

    el.removeAttribute('title'); // Prevent native tooltip

    const tooltipContainer = document.createElement('div');
    tooltipContainer.className = 'v-tooltip-container';
    tooltipContainer.style.position = 'absolute';
    tooltipContainer.style.pointerEvents = 'none';
    tooltipContainer.style.zIndex = '10000';
    tooltipContainer.style.opacity = '0';
    tooltipContainer.style.transition = 'opacity 0.3s ease-in-out';

    const tooltipContent = h('div', { class: 'v-tooltip-content' }, text);
    const app = createApp({ render: () => tooltipContent });
    app.mount(tooltipContainer);
    document.body.appendChild(tooltipContainer);
    tooltipAppMap.set(el, { app, tooltipContainer });

    const showTooltip = () => {
      const { top, left, width, height } = el.getBoundingClientRect();
      tooltipContainer.style.top = `${top + window.scrollY + height + 5}px`;
      tooltipContainer.style.left = `${left + window.scrollX + (width / 2) - (tooltipContainer.offsetWidth / 2)}px`;
      tooltipContainer.style.opacity = '1';
    };

    const hideTooltip = () => {
      tooltipContainer.style.opacity = '0';
    };

    el.addEventListener('mouseenter', showTooltip);
    el.addEventListener('mouseleave', hideTooltip);
    el._vTooltip_events = { showTooltip, hideTooltip }; // Store references for cleanup
  },
  unmounted(el) {
    const { app, tooltipContainer } = tooltipAppMap.get(el);
    if (app && tooltipContainer) {
      app.unmount();
      document.body.removeChild(tooltipContainer);
      tooltipAppMap.delete(el);
    }
    if (el._vTooltip_events) {
      el.removeEventListener('mouseenter', el._vTooltip_events.showTooltip);
      el.removeEventListener('mouseleave', el._vTooltip_events.hideTooltip);
      delete el._vTooltip_events;
    }
  }
};

export default vTooltip;

// 2. Register the directive globally (e.g., in main.js)
// import { createApp } from 'vue';
// import App from './App.vue';
// import vTooltip from './directives/vTooltip';
// const app = createApp(App);
// app.directive('tooltip', vTooltip);
// app.mount('#app');

// 3. Use the directive in a component
/*
<template>
  <button v-tooltip="'This is a helpful tooltip!'">
    Hover me for info
  </button>
  <span v-tooltip.top="'Another tooltip with specific placement (advanced logic needed)'" style="margin-left: 20px;">
    Hover this text
  </span>
  <p v-tooltip.bottom.right="'More tooltip options'" style="margin-left: 40px;">
    Paragraph with tooltip
  </p>
</template>

<script setup>
// No script needed for simple directive usage
</script>

<style>
.v-tooltip-content {
  background-color: #333;
  color: white;
  padding: 8px 12px;
  border-radius: 4px;
  white-space: nowrap;
  font-size: 0.85em;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
</style>
*/
How it works: This snippet demonstrates creating a custom `v-tooltip` directive in Vue 3. It leverages directive lifecycle hooks (`mounted`, `unmounted`) to attach and remove event listeners, dynamically create a tooltip element, and position it relative to the bound element. It uses `createApp` and `h` from Vue to render the tooltip's content as a small Vue application, ensuring full reactivity and proper cleanup when the element is removed from the DOM. This pattern allows for reusable and declarative UI enhancements.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs