Tooltip (подсказка)

Зачем нужно

Tooltip -- всплывающая подсказка, которая появляется при наведении или фокусе на элемент. Даёт дополнительную информацию, не загромождая интерфейс. Три подхода: CSS-only через ::after и attr, JS-позиционирование для сложных случаев и Popover API для современных браузеров.

Где используется

  • Подсказки к иконкам без текста
  • Пояснения к сокращениям и терминам
  • Дополнительная информация о элементе
  • Превью ссылки при наведении
  • Подсказки к полям формы

Подход 1: CSS-only Tooltip

Самый простой вариант -- чистый CSS с ::after и attr. Не требует JavaScript.

HTML

<button class="tooltip-trigger" data-tooltip="Сохранить документ">
  <svg width="20" height="20"><!-- иконка сохранения --></svg>
</button>

<p>
  Используйте <abbr class="tooltip-trigger" data-tooltip="Application Programming Interface">
  API</abbr> для интеграции.
</p>

CSS

.tooltip-trigger {
  position: relative;
  cursor: pointer;
}

/* Тело подсказки */
.tooltip-trigger::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 12px;
  background: #333;
  color: #fff;
  font-size: 13px;
  line-height: 1.4;
  border-radius: 6px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s;
  z-index: 100;
}

/* Стрелка */
.tooltip-trigger::before {
  content: '';
  position: absolute;
  bottom: calc(100% + 2px);
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: #333;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s;
  z-index: 100;
}

/* Показать при hover и focus */
.tooltip-trigger:hover::after,
.tooltip-trigger:hover::before,
.tooltip-trigger:focus::after,
.tooltip-trigger:focus::before {
  opacity: 1;
}

/* Варианты позиционирования */
.tooltip-trigger[data-position="bottom"]::after {
  bottom: auto;
  top: calc(100% + 8px);
}

.tooltip-trigger[data-position="bottom"]::before {
  bottom: auto;
  top: calc(100% + 2px);
  border-top-color: transparent;
  border-bottom-color: #333;
}

.tooltip-trigger[data-position="left"]::after {
  bottom: auto;
  left: auto;
  right: calc(100% + 8px);
  top: 50%;
  transform: translateY(-50%);
}

.tooltip-trigger[data-position="right"]::after {
  bottom: auto;
  left: calc(100% + 8px);
  top: 50%;
  transform: translateY(-50%);
}

Подход 2: JS Positioned Tooltip

Когда нужно точное позиционирование с учётом границ viewport.

HTML

<button class="has-tooltip" aria-describedby="tooltip-1"
        data-tooltip-text="Скопировать ссылку в буфер обмена">
  Копировать
</button>

<div id="tooltip-1" class="tooltip" role="tooltip" hidden>
  Скопировать ссылку в буфер обмена
</div>

CSS

.tooltip {
  position: fixed;
  padding: 6px 12px;
  background: #333;
  color: #fff;
  font-size: 13px;
  line-height: 1.4;
  border-radius: 6px;
  pointer-events: none;
  z-index: 9999;
  max-width: 250px;
  word-wrap: break-word;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);

  /* Анимация */
  opacity: 0;
  transition: opacity 0.15s;
}

.tooltip:not([hidden]) {
  opacity: 1;
}

JavaScript с логикой размещения

class Tooltip {
  constructor {
    this.tooltip = null;
    this.activeTarget = null;
    this.offset = 8; // Отступ от элемента (px)

    this.init;
  }

  init {
    document.addEventListener('mouseenter', (e) => {
      const target = e.target.closest('[data-tooltip-text]');
      if (target) this.show(target);
    }, true);

    document.addEventListener('mouseleave', (e) => {
      const target = e.target.closest('[data-tooltip-text]');
      if (target) this.hide;
    }, true);

    document.addEventListener('focusin', (e) => {
      const target = e.target.closest('[data-tooltip-text]');
      if (target) this.show(target);
    });

    document.addEventListener('focusout', (e) => {
      const target = e.target.closest('[data-tooltip-text]');
      if (target) this.hide;
    });

    // Скрыть по Escape
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') this.hide;
    });
  }

  show(target) {
    this.activeTarget = target;

    // Создать tooltip-элемент, если нет
    if (!this.tooltip) {
      this.tooltip = document.createElement('div');
      this.tooltip.classList.add('tooltip');
      this.tooltip.setAttribute('role', 'tooltip');
      document.body.appendChild(this.tooltip);
    }

    this.tooltip.textContent = target.dataset.tooltipText;
    this.tooltip.hidden = false;
    this.position(target);
  }

  hide {
    if (this.tooltip) {
      this.tooltip.hidden = true;
    }
    this.activeTarget = null;
  }

  position(target) {
    const rect = target.getBoundingClientRect();
    const tooltipRect = this.tooltip.getBoundingClientRect();

    // Пробуем разместить сверху
    let top = rect.top - tooltipRect.height - this.offset;
    let left = rect.left + (rect.width - tooltipRect.width) / 2;

    // Если не помещается сверху -- размещаем снизу
    if (top < 0) {
      top = rect.bottom + this.offset;
    }

    // Не вылезать за левый/правый край
    left = Math.max(8, Math.min(left, window.innerWidth - tooltipRect.width - 8));

    this.tooltip.style.top = `${top}px`;
    this.tooltip.style.left = `${left}px`;
  }
}

// Один экземпляр на всю страницу
new Tooltip();

Подход 3: Popover API (современные браузеры)

<button popovertarget="info-popover" popovertargetaction="toggle">
  Подробнее
</button>

<div id="info-popover" popover>
  <h3>Дополнительная информация</h3>
  <p>Этот popover может содержать любой HTML-контент,
     включая ссылки и интерактивные элементы.</p>
</div>
[popover] {
  padding: 16px;
  border: none;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  max-width: 320px;
}

[popover]::backdrop {
  background: transparent;
}

/* Анимация */
[popover]:popover-open {
  animation: fadeIn 0.15s ease-out;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

Разница: Tooltip -- кратковременная текстовая подсказка. Popover -- может содержать интерактивный контент (ссылки, кнопки, формы).


Accessibility-чеклист

Атрибут Назначение
role="tooltip" Элемент является подсказкой
aria-describedby Связывает элемент с tooltip
Появление по focus Работает с клавиатуры
Escape скрывает tooltip Управление с клавиатуры
Не перекрывает контент Tooltip не блокирует клики

Частые ошибки

Ошибка Проблема Решение
Только hover, нет focus Не работает с клавиатуры Добавь показ по focus
Tooltip обрезается viewport Вылезает за край экрана JS-логика перепозиционирования
title атрибут вместо tooltip Некрасивый, задержка, нет стилизации Кастомный tooltip
Слишком длинный текст Tooltip превращается в абзац Макс. 1-2 предложения
Нет aria-describedby Скринридер не озвучит Связь через ID

Связанные темы

Ресурсы