Skip to content

Tooltip Directive

The v-tooltip directive is used to display a tooltip when the user hovers over an element.

Usage

Both light and dark variants are available for the tooltip. toggle the dark mode to see the difference.

Video preview of the tooltip directive because of css issues in the docs.

View Code
vue
<script setup>
const tooltipText = "This is a tooltip";
const toggleDark = () => {
  document.documentElement.classList.toggle("dark");
};
</script>
<template>
  <main
    class="w-full rounded-2xl p-5 bg-gray-50 dark:bg-zinc-900 dark:text-white flex flex-col space-y-10 justify-center items-center"
  >
    <button @click="toggleDark">Dark</button>
    <div v-tooltip="tooltipText">Hover me for top tooltip</div>
    <div v-tooltip.right="tooltipText">Hover me for right tooltip</div>
    <div v-tooltip.left="tooltipText">Hover me for left tooltip</div>
    <div v-tooltip.bottom="tooltipText">Hover me for bottom tooltip</div>
    <div v-tooltip="tooltipText">Hover me for a tooltip (default top)</div>
  </main>
</template>
<style>
:root {
  --tooltip-arrow-size: 5px;
  --tooltip-arrow-bg: #f5f5f5;
  --tooltip-border: #e4e4e7;
}
/* dark */
.dark {
  --tooltip-arrow-bg: #27272a;
  --tooltip-border: #3d3d40;
}
.tooltip-top::after {
  content: " ";
  position: absolute;
  background-color: var(--tooltip-arrow-bg);
  border-right: 1px solid var(--tooltip-border);
  border-bottom: 1px solid var(--tooltip-border);
  width: 12px;
  height: 12px;
  left: 50%;
  margin-left: -5px;
  top: 100%;
  transform: rotate(45deg);
  translate: 0 -46%;
}
.tooltip-bottom::after {
  content: " ";
  position: absolute;
  background-color: var(--tooltip-arrow-bg);
  border-top: 1px solid var(--tooltip-border);
  border-left: 1px solid var(--tooltip-border);
  width: 12px;
  height: 12px;
  left: 50%;
  margin-left: -5px;
  bottom: 100%;
  transform: rotate(45deg);
  translate: 0 46%;
}

.tooltip-right::after {
  content: " ";
  position: absolute;
  background-color: var(--tooltip-arrow-bg);
  border-left: 1px solid var(--tooltip-border);
  border-bottom: 1px solid var(--tooltip-border);
  width: 12px;
  height: 12px;
  top: 50%;
  margin-top: -5px;
  right: 100%;
  transform: rotate(45deg);
  translate: 46% 0;
}

.tooltip-left::after {
  content: " ";
  position: absolute;
  background-color: var(--tooltip-arrow-bg);
  border-top: 1px solid var(--tooltip-border);
  border-right: 1px solid var(--tooltip-border);
  width: 12px;
  height: 12px;
  top: 50%;
  margin-top: -5px;
  left: 100%;
  transform: rotate(45deg);
  translate: -46% 0;
}
</style>

Arguments

The v-tooltip directive does not accept any arguments.

Modifiers

The v-tooltip directive takes the following modifiers:

ModifierDescription
leftDisplays the tooltip to the left of the element.
rightDisplays the tooltip to the right of the element.
topDisplays the tooltip above the element.
bottomDisplays the tooltip below the element.

Source Code

js
import { onBeforeUnmount } from "vue";

export const vToolTip = {
  mounted(el, binding) {
    // Create the tooltip element
    const tooltipEl = document.createElement("div");
    tooltipEl.classList.add("tooltip");
    tooltipEl.style.position = "absolute";
    tooltipEl.style.visibility = "hidden";
    tooltipEl.style.opacity = 0;
    tooltipEl.style.transition = "opacity 0.2s";
    tooltipEl.style.padding = "10px";
    tooltipEl.style.zIndex = 10;
    tooltipEl.style.textAlign = "center"; // Center the text

    // Add the arrow element
    const arrowEl = document.createElement("div");
    arrowEl.classList.add("tooltip-arrow");
    tooltipEl.appendChild(arrowEl);

    // Add the tooltip content
    tooltipEl.textContent = binding.value;

    // Position the tooltip
    const showTooltip = () => {
      tooltipEl.style.visibility = "visible";
      tooltipEl.style.opacity = 1;

      const elRect = el.getBoundingClientRect();
      const tooltipRect = tooltipEl.getBoundingClientRect();
      const arrowSize = 8;

      // handle all 4 positions by modifiers top, right, bottom, left
      if (binding.modifiers.top) {
        tooltipEl.classList.add("tooltip-top");
        tooltipEl.style.top = `${
          elRect.top - tooltipRect.height - arrowSize
        }px`;
        tooltipEl.style.left = `${
          elRect.left + elRect.width / 2 - tooltipRect.width / 2
        }px`;
        arrowEl.style.top = `${tooltipRect.height - 1}px`;
        arrowEl.style.left = `${tooltipRect.width / 2 - arrowSize}px`;
      } else if (binding.modifiers.right) {
        tooltipEl.classList.add("tooltip-right");
        tooltipEl.style.top = `${
          elRect.top + elRect.height / 2 - tooltipRect.height / 2
        }px`;
        tooltipEl.style.left = `${elRect.right + arrowSize}px`;
        arrowEl.style.top = `${tooltipRect.height / 2 - arrowSize}px`;
        arrowEl.style.left = `-${arrowSize}px`;
      } else if (binding.modifiers.bottom) {
        tooltipEl.classList.add("tooltip-bottom");
        tooltipEl.style.top = `${elRect.bottom + arrowSize}px`;
        tooltipEl.style.left = `${
          elRect.left + elRect.width / 2 - tooltipRect.width / 2
        }px`;
        arrowEl.style.top = `-${arrowSize}px`;
        arrowEl.style.left = `${tooltipRect.width / 2 - arrowSize}px`;
      } else if (binding.modifiers.left) {
        tooltipEl.classList.add("tooltip-left");
        tooltipEl.style.top = `${
          elRect.top + elRect.height / 2 - tooltipRect.height / 2
        }px`;
        tooltipEl.style.left = `${
          elRect.left - tooltipRect.width - arrowSize
        }px`;
        arrowEl.style.top = `${tooltipRect.height / 2 - arrowSize}px`;
        arrowEl.style.left = `${tooltipRect.width - 1}px`;
      } else {
        // default to top
        tooltipEl.classList.add("tooltip-top");
        tooltipEl.style.top = `${
          elRect.top - tooltipRect.height - arrowSize
        }px`;
        tooltipEl.style.left = `${
          elRect.left + elRect.width / 2 - tooltipRect.width / 2
        }px`;
        arrowEl.style.top = `${tooltipRect.height - 1}px`;
        arrowEl.style.left = `${tooltipRect.width / 2 - arrowSize}px`;
      }
    };

    const hideTooltip = () => {
      tooltipEl.style.visibility = "hidden";
      tooltipEl.style.opacity = 0;
    };

    el.addEventListener("mouseenter", showTooltip);
    el.addEventListener("mouseleave", hideTooltip);

    el.addEventListener("mouseenter", showTooltip);
    el.addEventListener("mouseleave", hideTooltip);

    // Ensure cleanup when component is unmounted
    onBeforeUnmount(() => {
      el.removeEventListener("mouseenter", showTooltip);
      el.removeEventListener("mouseleave", hideTooltip);
      el.removeChild(tooltipEl);
    });

    // Attach the tooltip and arrow elements to the element
    el.appendChild(tooltipEl);
  },
};