useMenu — это хук из библиотеки React Aria,
предназначенный для создания доступных, управляемых клавиатурой и
экранными ридерами выпадающих меню. Он обеспечивает
взаимодействие с меню на уровне ARIA-атрибутов, состояния и событий
клавиатуры, упрощая реализацию сложного поведения без необходимости
вручную следить за каждой деталью доступности.
Хук возвращает объекты свойств, которые нужно
распространять на корневой элемент меню и на элементы внутри меню, а
также контролирует состояние меню через
useMenuState.
Перед использованием useMenu необходимо создать
состояние меню с помощью хука useMenuState из
@react-stately/menu.
import { useMenuState } from "@react-stately/menu";
const menuState = useMenuState({
selectionMode: "single", // "single", "multiple" или "none"
disallowEmptySelection: true,
onSelectionChange: (key) => console.log(key)
});
Пояснения параметров:
selectionMode – определяет, как пользователь может выбирать элементы:
"single" — можно выбрать только один элемент;"multiple" — множественный выбор;"none" — выбор невозможен, меню используется только для
навигации.disallowEmptySelection — запрещает оставлять
выбор пустым, актуально при "single" или
"multiple".
onSelectionChange — колбэк при изменении выбранного элемента.
Хук useMenu принимает два аргумента:
aria-label).useMenuState.Пример использования:
import { useMenu } from "@react-aria/menu";
import { useMenuState } from "@react-stately/menu";
function DropdownMenu({ items }) {
const state = useMenuState({ items, selectionMode: "single" });
const { menuProps } = useMenu({}, state);
return (
<ul {...menuProps}>
{items.map(item => (
<li key={item.key}>{item.name}</li>
))}
</ul>
);
}
menuProps содержит все необходимые ARIA-атрибуты и обработчики событий:
role="menu" — обозначает элемент как меню для
скринридеров.aria-labelledby или aria-label — для
описания меню.Для каждого элемента меню рекомендуется использовать
useMenuItem:
import { useMenuItem } from "@react-aria/menu";
function MenuItem({ item, state }) {
const ref = React.useRef();
const { menuItemProps, isFocused, isSelected } = useMenuItem(
{ key: item.key },
state,
ref
);
return (
<li
{...menuItemProps}
ref={ref}
style={{
background: isFocused ? "lightgray" : "transparent",
fontWeight: isSelected ? "bold" : "normal"
}}
>
{item.name}
</li>
);
}
Основные свойства useMenuItem:
import React from "react";
import { useMenu } from "@react-aria/menu";
import { useMenuState } from "@react-stately/menu";
import { useMenuItem } from "@react-aria/menu";
function Menu({ items }) {
const state = useMenuState({ items, selectionMode: "single" });
const { menuProps } = useMenu({}, state);
return (
<ul {...menuProps} style={{ border: "1px solid black", padding: "0", listStyle: "none" }}>
{items.map(item => (
<MenuItem key={item.key} item={item} state={state} />
))}
</ul>
);
}
function MenuItem({ item, state }) {
const ref = React.useRef();
const { menuItemProps, isFocused, isSelected } = useMenuItem(
{ key: item.key },
state,
ref
);
return (
<li
{...menuItemProps}
ref={ref}
style={{
padding: "8px 16px",
background: isFocused ? "#e0e0e0" : "white",
fontWeight: isSelected ? "bold" : "normal",
cursor: "pointer"
}}
>
{item.name}
</li>
);
}
В этом примере реализована полная клавиатурная
поддержка: стрелки перемещают фокус, Enter выбирает элемент,
Esc закрывает меню (если оно обёрнуто в Popover).
useMenu часто используется вместе с
usePopover для создания выпадающих списков:
import { useOverlay, usePreventScroll } from "@react-aria/overlays";
function Dropdown({ buttonLabel, items }) {
const [isOpen, setIsOpen] = React.useState(false);
const state = useMenuState({ items, selectionMode: "single" });
const { menuProps } = useMenu({}, state);
return (
<div>
<button onCl ick={() => setIsOpen(!isOpen)}>{buttonLabel}</button>
{isOpen && (
<div
{...menuProps}
style={{
position: "absolute",
background: "white",
border: "1px solid black"
}}
>
{items.map(item => (
<MenuItem key={item.key} item={item} state={state} />
))}
</div>
)}
</div>
);
}
useMenu автоматически добавляет ARIA-атрибуты и
обработчики для доступности, в том числе управление фокусом и
клавиатурой, когда меню открыто внутри оверлея.
По умолчанию useMenu поддерживает:
Popover).Дополнительно можно настроить:
useMenuItem для элементов меню,
чтобы фокус и выбранное состояние корректно синхронизировались с
state.items меняются) убедиться, что
ключи элементов (key) уникальны.useOverlay и usePopover
повышает удобство и доступность меню.Таким образом, useMenu вместе с useMenuItem
и useMenuState позволяет создавать полностью
доступные и управляемые меню, с минимальными усилиями по
реализации ARIA-совместимости и клавиатурной навигации.