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 |
Связанные темы
- Popup модальное окно -- блокирующий диалог
- Dropdown меню -- интерактивный popup
- Скелетон загрузки -- визуальный placeholder
- Валидация формы в реальном времени -- ошибки как tooltip
Ресурсы
- MDN: Popover API
- WAI-ARIA Tooltip Pattern
- Floating UI Library -- библиотека позиционирования