Оверлеи
Тултип
Короткая подпись, появляющаяся при наведении и при фокусе. Единственный оверлей кита, сделанный без Popover API: обычным позиционированием, чтобы работать везде одинаково.
Разметка
<span class="inst-tooltip">
<button class="inst-btn inst-btn--icon inst-btn--ghost" type="button" aria-label="Обновить" aria-describedby="tt-refresh">
<svg class="inst-icon" aria-hidden="true"><use href="#i-refresh"/></svg>
</button>
<span class="inst-tooltip-text" role="tooltip" id="tt-refresh">Обновить</span>
</span>
<span class="inst-tooltip">
<button class="inst-btn inst-btn--ghost" type="button" aria-describedby="tt-pass">Что такое проход?</button>
<span class="inst-tooltip-text" role="tooltip" id="tt-pass">Один полный обход карты агентом</span>
</span>
Подпись центрируется по триггеру и не переворачивается: у обычного
позиционирования нет запасных мест. У края узкой области её обрежет ближайший
overflow: hidden — там нужен поповер.
Использование
<span class="inst-tooltip">
<button class="inst-btn" type="button" aria-describedby="tt-1">Что это</button>
<span class="inst-tooltip-text" role="tooltip" id="tt-1">Пояснение</span>
</span>
| Что | Обязательно | Почему |
|---|---|---|
role="tooltip" на подписи |
да | Иначе это просто спрятанный текст |
id на подписи |
да | На него ссылается триггер |
aria-describedby="id" на триггере |
да | Единственное, что связывает подпись с контролом. Без него тултип для скринридера не существует |
| Триггер фокусируем | да | Показ идёт через :focus-within. Нефокусируемому элементу нужен tabindex="0" |
Когда использовать
| Используйте | Возьмите другое |
|---|---|
| Пояснение к иконочной кнопке или сокращению | Текст, без которого нельзя обойтись — обычная подпись в потоке или сноска: тултип не открывается на сенсорном экране |
| Полное значение обрезанной подписи | Список действий — меню в поповере: в тултип нельзя навести курсор |
| Подсказка рядом с полем формы | Правило заполнения поля — подсказка формы, она видна всегда |
| Пояснение вне обрезающего контейнера | То же внутри панели с overflow: hidden — поповер: тултип там обрежется |
Варианты
Разметка
<span class="inst-tooltip">
<button class="inst-btn inst-btn--sm" type="button" aria-describedby="tt-short">Коротко</button>
<span class="inst-tooltip-text" role="tooltip" id="tt-short">19:38:04</span>
</span>
<span class="inst-tooltip" style="margin-inline-start:9rem">
<button class="inst-btn inst-btn--sm" type="button" aria-describedby="tt-wide">Подробно</button>
<span class="inst-tooltip-text inst-tooltip-text--wide" role="tooltip" id="tt-wide">Открытие модалки — единственная строка скрипта во всём разделе оверлеев; закрытие, подложка и блокировка прокрутки обходятся без него.</span>
</span>
Базовый тултип — в одну строку (white-space: nowrap). Это верно для подписи
из двух слов и ломается на предложении: строка растягивается за край экрана.
Поэтому длинному тексту нужен явный inst-tooltip-text--wide — перенос и
потолок ширины в 22rem.
Поведение
| Событие | Селектор |
|---|---|
| наведение мышью | .inst-tooltip:hover |
| фокус внутри обёртки | .inst-tooltip:focus-within |
Подпись не убирается из потока, а гасится прозрачностью, и на ней снята мышь
(pointer-events: none): курсор, идущий к тултипу, не перехватывается им и не
роняет наведение.
Тултип обрежется внутри контейнера с overflow: hidden. Он позиционируется
position: absolute относительно своей обёртки и не живёт в верхнем слое. Это
известная плата за то, что тултип работает одинаково всюду и не зависит от
поддержки якорного позиционирования.
Там, где обрезка возможна — внутри панели, ячейки таблицы, аккордеона, — берите поповер: он в верхнем слое, и его не срежет ничто.
Правила
Так Тултип на дополнение
Пояснение к иконочной кнопке, полное значение обрезанной подписи — то, без чего можно обойтись.
Не так Необходимое в тултипе
На сенсорном экране наведения нет. Текст, без которого нельзя работать, стоит в потоке или в сноске.
Так aria-describedby на триггере
Единственное, что связывает подпись с контролом. Без него тултипа для скринридера не существует.
Не так Тултип вместо доступного имени
aria-describedby — это описание. Имя иконочной кнопке даёт aria-label.
Доступность
| Клавиатура | Тултип появляется по :focus-within, то есть при табе на триггер. Без этого он был бы доступен только мыши |
| Сенсорный экран | Наведения там нет, а фокус приходит вместе с нажатием. Не кладите в тултип то, без чего нельзя обойтись |
| Не заменяет имя | aria-describedby — это описание. Доступное имя иконочной кнопке даёт aria-label, а не тултип |
| Перекрытие | Тултип стоит над триггером на --space-3: он не закрывает то, что поясняет |
| Уменьшенное движение | Переход прозрачности схлопывается вместе со всеми остальными |
| Печать | На листе не печатается |
API
| Имя | Значение | Что делает |
|---|---|---|
| класс | ||
inst-tooltip | — | Обёртка триггера. position: relative, отсюда считается положение |
inst-tooltip-text | — | Сама подпись. Над триггером, по центру |
| модификатор | ||
inst-tooltip-text--wide | — | Перенос строк и потолок ширины 22rem |
| токен | ||
--z-popover | 100 | |
--surface-overlay | light-dark(var(--n-0), var(--n-11)) | |
--shadow-popover | 0 4px 12px -2px var(--shadow-color-far), 0 2px 4px -2px var(--shadow-color-near) | |
--border | light-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11)) | |
--hairline | 1px | |
--radius-sm | 5px | |
--space-2 | 4px | |
--space-3 | 6px | |
--text-xs | 0.75rem | |
--leading-ui | 1.4 | |
--dur-1 | 80ms | |
--ease-out | cubic-bezier(0.22, 0.61, 0.36, 1) | |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument