Tooltip

Dit is één mogelijke implementatie, er zijn meerdere implementaties mogelijk.

Tooltips zijn een kleine informatievensters die verschijnen bij hover of focus van een element. Ze geven extra informatie zonder je interface vol te proppen.

Gebruik een tooltip:

  • Voor korte hulpteksten bij formuliervelden
  • Om icoontjes uit te leggen
  • Bij afkortingen of technische termen

Een goede tooltip:

  • Blijft staan als je eroverheen hovert
  • Verdwijnt niet vanzelf
  • Kan worden verborgen met Escape

Technische opbouw

  • Koppel de beschrijving met aria-describedby, gebruik het id van de tooltip
  • Geef de container van de tooltip de rol tooltip (role="tooltip")

Voorbeeld tooltip

HTML
<!-- Tooltip code -->
<div class="tooltip-container">
  <button id="help-button" aria-describedby="help-tooltip">
    Help
  </button>
  <div class="tooltip position-top" role="tooltip" id="help-tooltip" aria-hidden="true">
    Extra informatie
  </div>
</div>
CSS
/* Tooltip stijlen */
.tooltip-container {
  position: relative;
  display: inline-block;
}

/* Base tooltip styling */
.tooltip {
  position: absolute;
  background: black;
  color: white;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 0.8rem;
  white-space: nowrap;
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s ease-in-out;
  pointer-events: none;
}

/* Tooltip positioning - boven (voorkeur) */
.tooltip.position-top {
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 5px;
}

.tooltip.position-top::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: black;
}

/* Tooltip positioning - rechts (fallback) */
.tooltip.position-right {
  top: 50%;
  left: 100%;
  transform: translateY(-50%);
  margin-left: 5px;
}

.tooltip.position-right::after {
  content: '';
  position: absolute;
  top: 50%;
  right: 100%;
  transform: translateY(-50%);
  border: 5px solid transparent;
  border-right-color: black;
}

/* Zichtbare tooltip */
.tooltip.show {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* Voor grotere tooltips */
.tooltip.large {
  white-space: normal;
  max-width: 250px;
  width: max-content;
}
JavaScript
// Tooltip functionaliteit
class Tooltip {
  constructor() {
    this.tooltips = document.querySelectorAll('.tooltip-container');
    this.isTouch = 'ontouchstart' in window;
    this.activeTooltip = null;
    
    if (this.tooltips.length > 0) {
      this.init();
    }
  }

  init() {
    // Initialiseer alle tooltip containers
    this.tooltips.forEach(container => {
      const button = container.querySelector('button');
      const tooltip = container.querySelector('.tooltip');
      
      if (button && tooltip) {
        this.setupTooltip(button, tooltip);
      }
    });

    // Global escape key listener
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && this.activeTooltip) {
        this.hide(this.activeTooltip);
        this.activeTooltip.button.focus();
      }
    });

    // Close tooltip on outside click (touch devices)
    if (this.isTouch) {
      document.addEventListener('click', (e) => {
        if (this.activeTooltip && !this.activeTooltip.container.contains(e.target)) {
          this.hide(this.activeTooltip);
        }
      });
    }
  }

  setupTooltip(button, tooltip) {
    const container = button.closest('.tooltip-container');
    const tooltipData = { button, tooltip, container, showTimeout: null, hideTimeout: null };

    // ARIA setup
    button.setAttribute('aria-describedby', tooltip.id);

    if (!this.isTouch) {
      // Mouse events voor desktop
      button.addEventListener('mouseenter', () => this.show(tooltipData));
      button.addEventListener('mouseleave', () => this.hide(tooltipData));
      
      // Prevent hiding when hovering over tooltip
      tooltip.addEventListener('mouseenter', () => clearTimeout(tooltipData.hideTimeout));
      tooltip.addEventListener('mouseleave', () => this.hide(tooltipData));
    }

    // Keyboard events
    button.addEventListener('focus', () => this.show(tooltipData));
    button.addEventListener('blur', () => this.hide(tooltipData));

    // Touch events
    if (this.isTouch) {
      button.addEventListener('click', (e) => {
        e.preventDefault();
        if (this.activeTooltip === tooltipData) {
          this.hide(tooltipData);
        } else {
          if (this.activeTooltip) {
            this.hide(this.activeTooltip);
          }
          this.show(tooltipData, 0);
        }
      });
    }
  }

  checkPosition(tooltipData) {
    const { button, tooltip } = tooltipData;
    const buttonRect = button.getBoundingClientRect();
    const viewportWidth = window.innerWidth;
    const viewportHeight = window.innerHeight;
    
    // Bereken beschikbare ruimte
    const spaceAbove = buttonRect.top;
    const spaceRight = viewportWidth - buttonRect.right;
    
    // Geschatte tooltip dimensies (als niet zichtbaar)
    let tooltipWidth = tooltip.offsetWidth || 200;
    let tooltipHeight = tooltip.offsetHeight || 40;
    
    // Als tooltip zichtbaar is, gebruik werkelijke dimensies
    if (tooltip.classList.contains('show')) {
      const tooltipRect = tooltip.getBoundingClientRect();
      tooltipWidth = tooltipRect.width;
      tooltipHeight = tooltipRect.height;
    }
    
    // Check beschikbare ruimte (met margin)
    const hasSpaceAbove = spaceAbove >= (tooltipHeight + 10);
    const hasSpaceRight = spaceRight >= (tooltipWidth + 10);
    
    // Verwijder bestaande position classes
    tooltip.classList.remove('position-top', 'position-right');
    
    // Kies positie: eerst boven proberen, dan rechts
    if (hasSpaceAbove) {
      tooltip.classList.add('position-top');
    } else if (hasSpaceRight) {
      tooltip.classList.add('position-right');
    } else {
      // Fallback: boven (ook al past het niet perfect)
      tooltip.classList.add('position-top');
    }
  }

  show(tooltipData, delay = 500) {
    clearTimeout(tooltipData.hideTimeout);
    
    // Hide any other active tooltip
    if (this.activeTooltip && this.activeTooltip !== tooltipData) {
      this.hide(this.activeTooltip);
    }
    
    tooltipData.showTimeout = setTimeout(() => {
      // Check en stel positie in voordat we tonen
      this.checkPosition(tooltipData);
      
      tooltipData.tooltip.classList.add('show');
      tooltipData.tooltip.setAttribute('aria-hidden', 'false');
      this.activeTooltip = tooltipData;
      
      // Hercheck positie na rendering voor betere precisie
      setTimeout(() => this.checkPosition(tooltipData), 10);
    }, delay);
  }

  hide(tooltipData) {
    clearTimeout(tooltipData.showTimeout);
    tooltipData.hideTimeout = setTimeout(() => {
      tooltipData.tooltip.classList.remove('show');
      tooltipData.tooltip.setAttribute('aria-hidden', 'true');
      if (this.activeTooltip === tooltipData) {
        this.activeTooltip = null;
      }
    }, 100);
  }
}

// Initialiseer tooltip systeem
new Tooltip();

Toetsenbord

  • Escape:
    • Bij een uitgevouwen tooltip: sluit de tooltip

Checklist

Component

  • (4.1.2) De tooltip container moet de rol tooltip hebben (role="tooltip")
  • (1.3.1) Het element met tooltip moet aria-describedby hebben met het id van de bijbehorende tooltip

Bronnen