Vue Custom Directive for Themed Tooltips

This direcitve creates a tooltip that dynamcially adjusts its appearance based on the system's color scheme.

First, detect the system's color preference:

let systemMode = 'light'; // Default to light mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    systemMode = 'dark';
}

Next, implement the custom directive. A critical detail is using position: fixed for the tooltip element. Using position: absolute can lead to incorrect positioning when the page has scrollbars.

A timer is included for delayed display; this can be omitted if not required.

// tooltip.js

let tooltipTimer = null;

const tooltipDirective = {
  bind(element, binding) {
    element.addEventListener('mouseenter', (event) => {
      tooltipTimer = setTimeout(() => {
        const tooltipElement = document.createElement('div');
        const isDarkMode = element.getAttribute('effect') === 'dark';
        
        tooltipElement.style.cssText = `
          overflow: auto;
          position: fixed;
          background: ${isDarkMode ? '#333' : '#fff'};
          color: ${isDarkMode ? '#fff' : '#333'};
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
          border-radius: 4px;
          padding: 8px 12px;
          font-size: 14px;
          z-index: 1000;
          top: ${event.clientY + 15}px;
          left: ${event.clientX + 10}px;
        `;
        tooltipElement.id = 'vue-tooltip';
        tooltipElement.textContent = element.getAttribute('tip');
        document.body.appendChild(tooltipElement);
      }, 800);
    });

    element.addEventListener('mouseleave', () => {
      const existingTooltip = document.getElementById('vue-tooltip');
      if (existingTooltip) {
        document.body.removeChild(existingTooltip);
      }
      clearTimeout(tooltipTimer);
      tooltipTimer = null;
    });

    // Optional: Update position on mouse move
    // element.addEventListener('mousemove', (event) => {
    //   const existingTooltip = document.getElementById('vue-tooltip');
    //   if (existingTooltip) {
    //     existingTooltip.style.top = `${event.clientY + 15}px`;
    //     existingTooltip.style.left = `${event.clientX + 10}px`;
    //   }
    // });
  },
  unbind(element) {
    // Clean up event listeners if necessary, though the mouseleave handler should suffice.
    element.removeEventListener('mouseenter', () => {});
    element.removeEventListener('mouseleave', () => {});
  }
};

export default {
  install(Vue) {
    Vue.directive('tooltip', tooltipDirective);
  }
};

Register the directive globlaly:

import Vue from 'vue';
import TooltipDirective from './tooltip.js';

Vue.use(TooltipDirective);

Use the directive in your templates:

<template>
  <span v-tooltip :tip='item.title' :effect='systemMode'>
    {{ item.title }}
  </span>
</template>

<script>
export default {
  data() {
    return {
      systemMode: 'light', // Initialize with default
      // ... other data properties
    };
  },
  mounted() {
    // Update systemMode when the component mounts
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
      this.systemMode = 'dark';
    } else {
      this.systemMode = 'light';
    }
  }
};
</script>

Tags: Vue.js Custom Directives Tooltips Frontend Development Web Components

Posted on Mon, 29 Jun 2026 16:24:17 +0000 by MrRosary