Skip to content

useListNavigation

添加基于箭头键的项目列表导航,使用真实 DOM 焦点或虚拟焦点。

¥Adds arrow key-based navigation of a list of items, either using real DOM focus or virtual focus.

import {useListNavigation} from '@floating-ui/react';

这对于创建包含项目列表(例如菜单、选择或组合框)的浮动元素非常有用,以确保键盘使用可以导航列表。

¥This is useful for creating floating elements that contain a list of items (such as a menu, select, or combobox) to ensure that keyboard usage can navigate the list.

  • 有关创建可组合子 API 组件的信息,请参阅 FloatingList

    ¥See FloatingList for creating composable children API components.

  • 有关浮动元素上下文之外的列表导航,请参阅 Composite

    ¥See Composite for list navigation outside of floating element contexts.

用法

¥Usage

此 Hook 返回事件处理程序 props 和 ARIA 属性 props。

¥This Hook returns event handler props and ARIA attribute props.

要使用它,请将 useFloating() 返回的 context 对象传递给它,然后将其结果输入 useInteractions() 数组。然后将返回的 prop getter 传播到元素上进行渲染。getItemProps() 分布到每个列表项。

¥To use it, pass it the context object returned from useFloating(), and then feed its result into the useInteractions() array. The returned prop getters are then spread onto the elements for rendering. getItemProps() is spread to each list item.

listRef 包含 HTML 元素数组。activeIndex 确定列表的哪个索引当前处于活动状态(聚焦或高亮)。

¥The listRef holds an array of HTML elements. The activeIndex determines which index of the list is currently active (focused or highlighted).

当使用真正的 DOM 焦点(默认)时,列表项必须是可聚焦的,并且应该根据浮动元素的角色具有适当的 role 属性。

¥When using real DOM focus (default), the list items must be focusable and should have an appropriate role prop based on the role of the floating element.

function App() {
  const [activeIndex, setActiveIndex] = useState(null);
 
  const {refs, floatingStyles, context} = useFloating({
    open: true,
  });
 
  const listRef = useRef([]);
 
  const listNavigation = useListNavigation(context, {
    listRef,
    activeIndex,
    onNavigate: setActiveIndex,
  });
 
  const {getReferenceProps, getFloatingProps, getItemProps} =
    useInteractions([listNavigation]);
 
  const items = ['one', 'two', 'three'];
 
  return (
    <>
      <div ref={refs.setReference} {...getReferenceProps()}>
        Reference element
      </div>
      <div
        ref={refs.setFloating}
        style={floatingStyles}
        {...getFloatingProps()}
      >
        {items.map((item, index) => (
          <div
            key={item}
            // Make these elements focusable using a roving tabIndex.
            tabIndex={activeIndex === index ? 0 : -1}
            ref={(node) => {
              listRef.current[index] = node;
            }}
            {...getItemProps()}
          >
            {item}
          </div>
        ))}
      </div>
    </>
  );
}

示例

¥Examples

FloatingFocusManager 一起使用

¥Using with FloatingFocusManager

useListNavigation()<FloatingFocusManager> 都管理焦点,但方式不同。为了确保它们正常协同工作,需要考虑最初的焦点。

¥useListNavigation() and <FloatingFocusManager> both manage focus but in different ways. To ensure they work together properly, the initial point of focus needs to be considered.

默认情况下,焦点管理器将焦点集中在浮动元素内的第一个可选项(不可聚焦)元素。如果没有,它就会退回到浮动元素本身。这允许 keydown 事件适用于指针输入(例如,用鼠标打开,然后用箭头键开始导航)。

¥The focus manager by default focuses the first tabbable (not focusable) element inside of the floating element. If none are, it falls back to the floating element itself. This allows keydown events to work for pointer input (e.g. open with mouse, then start navigating with arrow keys).

  1. 对于组合框,输入应保持焦点,将其设置为 -1,以便焦点根本不会移动:

    ¥For a combobox, where the input should keep focus, set it to -1 so focus doesn’t move at all:

<FloatingFocusManager context={context} initialFocus={-1}>
  {/* floating element */}
</FloatingFocusManager>
  1. 对于其他类型的组件,例如菜单或选择,你希望焦点在浮动元素内移动,默认值有效,但如果 activeIndexnull,请确保你的列表项不可选项卡。

    ¥For other types of components, like a menu or a select, where you want focus to move inside the floating element, the default value works, but make sure your list items aren’t tabbable if the activeIndex is null.

// Not focusable, not tabbable.
<div />
// Tabbable and focusable.
<div tabIndex={0} />
// Not tabbable, but focusable.
<div tabIndex={-1} />

推荐策略为流动 tabIndex:

¥A roving tabIndex is the recommended strategy:

<div tabIndex={activeIndex === index ? 0 : -1} />

属性

¥Props

interface UseListNavigationProps {
  listRef: React.MutableRefObject<Array<HTMLElement | null>>;
  activeIndex: number | null;
  onNavigate?(index: number | null): void;
  enabled?: boolean;
  selectedIndex?: number | null;
  loop?: boolean;
  nested?: boolean;
  rtl?: boolean;
  virtual?: boolean;
  virtualItemRef?: React.MutableRefObject<HTMLElement | null>;
  allowEscape?: boolean;
  orientation?: 'vertical' | 'horizontal' | 'both';
  cols?: number;
  focusItemOnOpen?: 'auto' | boolean;
  focusItemOnHover?: boolean;
  openOnArrowKeyDown?: boolean;
  disabledIndices?: Array<number>;
  scrollItemIntoView?: boolean | ScrollIntoViewOptions;
  itemSizes?: Dimensions[];
  dense?: boolean;
}

listRef

Required

默认:空列表

¥default: empty list

保存列表项数组的 ref。你可以按索引分配数组中的每个项目,如下所示:

¥A ref that holds an array of list items. You can assign each item in the array by its index like so:

const options = ['one', 'two', 'three'];
const listRef = useRef([]);
 
return options.map((option, index) => (
  <li
    key={option}
    ref={(node) => {
      listRef.current[index] = node;
    }}
  >
    {option}
  </li>
));

activeIndex

Required

默认:null

¥default: null

当前活动的(即高亮或聚焦的)项目索引,可以选择也可以不选择。

¥The currently active (i.e. highlighted or focused) item index, which may or may not be selected.

const [activeIndex, setActiveIndex] = useState(null);
 
useListNavigation(context, {
  activeIndex,
});

onNavigate

默认:no-op

¥default: no-op

用户导航时调用的回调,传入当前 activeIndex

¥Callback invoked when the user navigates, passed in the current activeIndex.

const [activeIndex, setActiveIndex] = useState(null);
 
useListNavigation(context, {
  onNavigate: setActiveIndex,
});

enabled

默认:true

¥default: true

有条件地启用/禁用 Hook。

¥Conditionally enable/disable the Hook.

useListNavigation(context, {
  enabled: false,
});

selectedIndex

默认:null

¥default: null

当前选定的项目索引,可能处于活动状态,也可能处于非活动状态。

¥The currently selected item index, which may or may not be active.

这是触发按钮/输入中显示的项目。

¥This is the item shown in the trigger button/input.

const [selectedIndex, setSelectedIndex] = useState(null);
 
useListNavigation(context, {
  selectedIndex,
});

loop

默认:false

¥default: false

确定当导航经过第一个或最后一个项目时焦点是否应循环。

¥Determines whether focus should loop around when navigating past the first or last item.

useListNavigation(context, {
  loop: true,
});

nested

默认:false

¥default: false

如果列表嵌套在另一个列表中(例如嵌套子菜单),则导航语义会发生变化。

¥If the list is nested within another one (e.g. a nested submenu), the navigation semantics change.

useListNavigation(context, {
  nested: true,
});

rtl

默认:false

¥default: false

浮动元素的导航方向是否为 RTL 布局。

¥Whether the direction of the floating element’s navigation is in RTL layout.

useListNavigation(context, {
  rtl: true,
});

virtual

默认:false

¥default: false

焦点是否为虚拟(使用 aria-activedescendant)。

¥Whether the focus is virtual (using aria-activedescendant).

如果你需要将焦点保留在参考元素(例如输入)上,但允许箭头键导航列表项,请使用此选项。这在自动补齐列表框组件中很常见。

¥Use this if you need focus to remain on the reference element (such as an input), but allow arrow keys to navigate list items. This is common in autocomplete listbox components.

useListNavigation(context, {
  virtual: true,
});

virtualItemRef

默认:undefined

¥default: undefined

当使用虚拟焦点管理时,它保存对虚拟焦点项目的引用。

¥When using virtual focus management, this holds a ref to the virtually-focused item.

这允许启用嵌套虚拟导航,并让你知道何时从处理事件的根引用虚拟聚焦嵌套元素。

¥This allows nested virtual navigation to be enabled, and lets you know when a nested element is virtually focused from the root reference handling the events.

const virtualItemRef = useRef(null);
useListNavigation(context, {
  virtualItemRef,
});

allowEscape

确定焦点是否可以脱离列表,以便在导航超出列表边界后不会选择任何内容。在某些自动补齐/组合框组件中,这可能是需要的,因为屏幕阅读器将返回到输入。

¥Determines whether focus can escape the list, such that nothing is selected after navigating beyond the boundary of the list. In some autocomplete/combobox components, this may be desired, as screen readers will return to the input.

useListNavigation(context, {
  loop: true,
  allowEscape: true,
});

orientation

默认:'vertical'

¥default: 'vertical'

导航发生的方向。

¥The orientation in which navigation occurs.

useListNavigation(context, {
  orientation: 'horizontal',
});

cols

默认:1

¥default: 1

指定列表有多少列(即,它是一个网格)。

¥Specifies how many columns the list has (i.e., it’s a grid).

使用 'horizontal' 方向(例如,对于表情符号选择器/日期选择器,按 ArrowRight 或 ArrowLeft 可以更改行)或 'both'(当前行不能使用 ArrowRight 或 ArrowLeft 转义,只能使用 ArrowUp 和 ArrowDown)。

¥Use an orientation of 'horizontal' (e.g. for an emoji picker/date picker, where pressing ArrowRight or ArrowLeft can change rows), or 'both' (where the current row cannot be escaped with ArrowRight or ArrowLeft, only ArrowUp and ArrowDown).

useListNavigation(context, {
  // 4 columns, any number of rows
  cols: 4,
});

focusItemOnOpen

默认:'auto'

¥default: 'auto'

是否在打开浮动元素时使项目聚焦。'auto' 根据输入类型(键盘与指针)推断要做什么,而布尔值将强制该值。

¥Whether to focus the item upon opening the floating element. 'auto' infers what to do based on the input type (keyboard vs. pointer), while a boolean value will force the value.

useListNavigation(context, {
  focusItemOnOpen: true,
});

focusItemOnHover

默认:true

¥default: true

悬停项目是否同步焦点。

¥Whether hovering an item synchronizes the focus.

useListNavigation(context, {
  focusItemOnHover: false,
});

openOnArrowKeyDown

默认:true

¥default: true

按导航主轴上的箭头键是否打开浮动元素。

¥Whether pressing an arrow key on the navigation’s main axis opens the floating element.

useListNavigation(context, {
  openOnArrowKeyDown: false,
});

disabledIndices

默认:undefined

¥default: undefined

默认情况下,具有 disabledaria-disabled 属性的元素在列表导航中会被跳过 - 但是,这需要渲染这些项目。

¥By default elements with either a disabled or aria-disabled attribute are skipped in the list navigation — however, this requires the items to be rendered.

此属性允许你手动指定应禁用的索引,覆盖默认逻辑。

¥This prop allows you to manually specify indices which should be disabled, overriding the default logic.

对于 Windows 样式的选择菜单(通过箭头键导航时不会打开菜单),请指定一个空数组。

¥For Windows-style select menus, where the menu does not open when navigating via arrow keys, specify an empty array.

useListNavigation(context, {
  disabledIndices: [0, 3],
});

scrollItemIntoView

默认:true | ScrollIntoViewOptions

¥default: true | ScrollIntoViewOptions

导航时是否将活动项目滚动到视图中。默认值使用 nearest 选项。

¥Whether to scroll the active item into view when navigating. The default value uses nearest options.

useListNavigation(context, {
  scrollItemIntoView: false,
});

itemSizes

默认:undefined

¥default: undefined

仅适用于网格导航,一个 Dimensions 对象的数组,它指定每个项目的宽度(列数)和高度(行数),因此导航算法可以考虑可变大小。如果未指定,则每个项目都将被视为大小为 1 行 1 列。

¥Only for grid navigation, an array of Dimensions objects, which specify the width (number of columns) and height (number of rows) of each item, so the navigation algorithm can take the variable sizes into account. If not specified, every item will be treated as if it has a size of 1 row and 1 column.

useListNavigation(context, {
  itemSizes: [
    {width: 2, height: 2},
    {width: 1, height: 3},
  ],
});

dense

默认:false

¥default: false

仅针对网格导航,确定网格定位算法是否应遵循 CSS Grid 的 auto-flow dense 算法。

¥Only for grid navigation, determines if the grid positioning algorithm should follow CSS Grid’s auto-flow dense algorithm.

useListNavigation(context, {
  dense: true,
});