useTooltip для подсказок

useTooltip — это хук из библиотеки React Aria, предназначенный для создания доступных подсказок, которые корректно взаимодействуют с клавиатурой, экранными читалками и другими средствами доступности. Подсказка, реализованная через useTooltip, автоматически учитывает роли ARIA, управление фокусом и позиционирование.


Импорт и базовое использование

Для начала нужно импортировать хук из библиотеки:

import { useTooltip, useTooltipTrigger } from '@react-aria/tooltip';
import { useOverlayPosition } from '@react-aria/overlays';
import { useRef } from 'react';

useTooltip не работает самостоятельно — он всегда используется вместе с триггером, который показывает подсказку. Для триггера используется useTooltipTrigger.

function TooltipExample() {
  const ref = useRef();
  const { triggerProps, tooltipProps, isOpen } = useTooltipTrigger({
    delay: 500 // задержка перед показом подсказки
  }, useTooltip);

  return (
    <div>
      <button {...triggerProps} ref={ref}>
        Наведи на меня
      </button>
      {isOpen && <Tooltip {...tooltipProps}>Это подсказка</Tooltip>}
    </div>
  );
}

Здесь ключевые моменты:

  • triggerProps — свойства, которые нужно применить к элементу-триггеру.
  • tooltipProps — свойства для элемента подсказки.
  • isOpen — состояние, показывающее, отображается ли подсказка.
  • Задержка (delay) позволяет предотвратить мгновенное появление подсказки при случайном наведении.

Создание компонента Tooltip

Подсказка должна быть отдельным компонентом для удобного повторного использования:

function Tooltip({ children, ...props }) {
  return (
    <div
      role="tooltip"
      style={{
        background: 'black',
        color: 'white',
        padding: '4px 8px',
        borderRadius: '4px',
        position: 'absolute',
        zIndex: 1000
      }}
      {...props}
    >
      {children}
    </div>
  );
}

Особенности:

  • Атрибут role="tooltip" обязателен для корректной работы экранных читалок.
  • position: absolute необходим для точного размещения относительно триггера.
  • Стили можно изменять или интегрировать с системами позиционирования (например, Popper.js или встроенным useOverlayPosition).

Позиционирование с useOverlayPosition

Для динамического позиционирования подсказки относительно триггера используется useOverlayPosition:

import { useOverlayPosition } from '@react-aria/overlays';

function Tooltip({ children, triggerRef, isOpen }) {
  const overlayRef = useRef();
  const { overlayProps, placement } = useOverlayPosition({
    targetRef: triggerRef,
    overlayRef,
    placement: 'top',
    offset: 4
  });

  if (!isOpen) return null;

  return (
    <div
      {...overlayProps}
      ref={overlayRef}
      role="tooltip"
      style={{
        background: 'black',
        color: 'white',
        padding: '4px 8px',
        borderRadius: '4px',
        position: 'absolute',
        zIndex: 1000
      }}
    >
      {children}
    </div>
  );
}

Ключевые параметры useOverlayPosition:

  • targetRef — ссылка на триггер, относительно которого позиционируется подсказка.
  • overlayRef — ссылка на саму подсказку.
  • placement — позиция подсказки (top, bottom, left, right).
  • offset — смещение между триггером и подсказкой.
  • isOpen — состояние видимости, переданное извне.

Управление видимостью

useTooltipTrigger автоматически управляет состоянием подсказки. В дополнение можно использовать кастомное управление:

const [isOpen, setOpen] = useState(false);

const { triggerProps, tooltipProps } = useTooltipTrigger(
  { isOpen, onOpenChange: setOpen, delay: 500 },
  useTooltip
);

Преимущества такого подхода:

  • Возможность синхронизации подсказок с другими элементами интерфейса.
  • Полный контроль над временем открытия и закрытия.

Поддержка клавиатуры и экранных читалок

React Aria обеспечивает правильные роли и управление фокусом:

  • Подсказка появляется при фокусе на триггере или наведении мыши.
  • Атрибут aria-describedby автоматически связывает триггер с подсказкой.
  • Поддерживаются задержки для плавного UX и предотвращения «мигания» подсказки при быстрых движениях мыши.

Пример:

<button {...triggerProps} ref={ref} aria-describedby="tooltip1">
  Наведи или сфокусируйся
</button>
<Tooltip id="tooltip1" {...tooltipProps} triggerRef={ref} isOpen={isOpen}>
  Это подсказка для клавиатуры и мыши
</Tooltip>

Доступность и UX

  • Всегда использовать role="tooltip" и aria-describedby для соответствия стандартам WCAG.
  • Подсказка должна быть короткой и информативной.
  • Добавление задержки (delay) улучшает опыт пользователя и предотвращает лишние всплытия.
  • Позиционирование должно учитывать границы окна, чтобы подсказка не обрезалась.

Пример полной интеграции

function TooltipDemo() {
  const triggerRef = useRef();
  const { triggerProps, tooltipProps, isOpen } = useTooltipTrigger({ delay: 300 }, useTooltip);

  return (
    <div style={{ margin: '100px' }}>
      <button {...triggerProps} ref={triggerRef}>
        Наведи или сфокусируйся
      </button>
      {isOpen && (
        <Tooltip triggerRef={triggerRef} {...tooltipProps} isOpen={isOpen}>
          Подсказка с правильным позиционированием и доступностью
        </Tooltip>
      )}
    </div>
  );
}

Такой подход объединяет контроль видимости, позиционирование и доступность в одном удобном паттерне, который масштабируется на большие проекты и обеспечивает качественный UX для всех пользователей.