Skip to content

useListNavigation

Adds focus-managed indexed navigation via arrow keys to a list of items within the floating element.

import {
  useFloating,
  useInteractions,
  useListNavigation,
} from '@floating-ui/react-dom-interactions';
 
// ...
const {context} = useFloating();
const {getReferenceProps, getFloatingProps, getItemProps} =
  useInteractions([
    useListNavigation(context, {
      // props
    }),
  ]);

Spread the getItemProps to each list item.

Props

interface Props {
  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;
  allowEscape?: boolean;
  orientation?: 'vertical' | 'horizontal' | 'both';
  focusItemOnOpen?: 'auto' | boolean;
  focusItemOnHover?: boolean;
  openOnArrowKeyDown?: boolean;
  disabledIndices?: Array<number>;
}

listRef

default: empty list

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

default: null

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

In a <Select />, this is the item that's currently highlighted (focused) but not selected. The user may have intent to select this item.

onNavigate

default: no-op

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

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

enabled

default: true

Conditionally enable/disable the hook.

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

selectedIndex

default: null

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

In a <Select />, this is the item shown in the trigger button.

loop

default: false

Whether to restart from the beginning or end if the user has navigated to the boundary of the list.

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

nested

default: false

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

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

rtl

default: false

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

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

virtual

default: false

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,
});

Your virtually-focused list items must have a unique id set on them.

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.

loop must be true

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

orientation

default: 'vertical'

The orientation in which navigation occurs.

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

focusItemOnOpen

default: '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

default: true

Whether hovering an item synchronizes the focus.

openOnArrowKeyDown

default: true

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

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

disabledIndices

default: openOnArrowKeyDown ? undefined : []

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.

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