System for creating accessible lists, menus, and composite widgets with keyboard navigation, virtual focus, type-ahead search, and proper ARIA support.
Provides arrow key navigation for lists of items with real or virtual focus management.
/**
* Arrow key navigation for lists with real or virtual focus
* @param context - Floating UI context
* @param props - List navigation configuration
* @returns Element props for list navigation handling
*/
function useListNavigation(
context: FloatingRootContext,
props: UseListNavigationProps
): ElementProps;
interface UseListNavigationProps {
listRef: React.MutableRefObject<Array<HTMLElement | null>>;
activeIndex: number | null;
onNavigate?: (activeIndex: number | null) => void;
enabled?: boolean;
orientation?: 'vertical' | 'horizontal' | 'both';
loop?: boolean;
nested?: boolean;
focusItemOnOpen?: boolean | 'auto';
focusItemOnHover?: boolean;
cols?: number;
disabledIndices?: Array<number>;
allowEscape?: boolean;
itemSizes?: Array<number>;
dense?: boolean;
rtl?: boolean;
virtual?: boolean;
virtualItemRef?: React.MutableRefObject<HTMLElement | null>;
}Usage Examples:
import {
useListNavigation,
useFloating,
useClick,
useInteractions,
FloatingList,
useListItem
} from '@floating-ui/react';
import { useState, useRef } from 'react';
// Basic list navigation
function Menu() {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const listRef = useRef<Array<HTMLElement | null>>([]);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
});
const click = useClick(context);
const listNavigation = useListNavigation(context, {
listRef,
activeIndex,
onNavigate: setActiveIndex,
});
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([
click,
listNavigation,
]);
const items = ['Item 1', 'Item 2', 'Item 3'];
return (
<>
<button ref={refs.setReference} {...getReferenceProps()}>
Menu
</button>
{isOpen && (
<FloatingList elementsRef={listRef} labelsRef={undefined}>
<div
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
>
{items.map((item, index) => (
<MenuItem
key={item}
label={item}
active={activeIndex === index}
{...getItemProps({
onClick() {
setIsOpen(false);
},
})}
/>
))}
</div>
</FloatingList>
)}
</>
);
}
function MenuItem(props: {
label: string;
active: boolean;
[key: string]: any;
}) {
const { label, active, ...rest } = props;
const { ref, index } = useListItem({ label });
return (
<div
ref={ref}
role="menuitem"
tabIndex={active ? 0 : -1}
style={{
background: active ? 'lightblue' : 'white',
padding: '8px',
}}
{...rest}
>
{label}
</div>
);
}
// Grid navigation
function GridMenu() {
const [activeIndex, setActiveIndex] = useState(0);
const listRef = useRef<Array<HTMLElement | null>>([]);
const { refs, context } = useFloating({
open: true,
onOpenChange: () => {},
});
const listNavigation = useListNavigation(context, {
listRef,
activeIndex,
onNavigate: setActiveIndex,
orientation: 'both',
cols: 3,
loop: true,
});
const { getFloatingProps, getItemProps } = useInteractions([listNavigation]);
const items = Array.from({ length: 9 }, (_, i) => `Item ${i + 1}`);
return (
<div
ref={refs.setFloating}
style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}
{...getFloatingProps()}
>
{items.map((item, index) => (
<div
key={item}
ref={(el) => { listRef.current[index] = el; }}
tabIndex={activeIndex === index ? 0 : -1}
style={{
background: activeIndex === index ? 'lightblue' : 'white',
padding: '8px',
border: '1px solid gray',
}}
{...getItemProps()}
>
{item}
</div>
))}
</div>
);
}Provides type-to-search functionality for focusing items in lists by typing their labels.
/**
* Type-to-search functionality for focusing items
* @param context - Floating UI context
* @param props - Typeahead configuration
* @returns Element props for typeahead handling
*/
function useTypeahead(
context: FloatingRootContext,
props: UseTypeaheadProps
): ElementProps;
interface UseTypeaheadProps {
listRef: React.MutableRefObject<Array<HTMLElement | null>>;
activeIndex: number | null;
onMatch?: (index: number) => void;
onTypingChange?: (typing: boolean) => void;
enabled?: boolean;
findMatch?: (
list: Array<string | null>,
typedString: string
) => string | null | undefined;
resetMs?: number;
ignoreKeys?: Array<string>;
selectedIndex?: number | null;
}Usage Example:
import { useTypeahead, useListNavigation } from '@floating-ui/react';
function SearchableMenu() {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const [typing, setTyping] = useState(false);
const listRef = useRef<Array<HTMLElement | null>>([]);
const labelRef = useRef<Array<string | null>>([]);
const { refs, context } = useFloating({
open: true,
onOpenChange: () => {},
});
const listNavigation = useListNavigation(context, {
listRef,
activeIndex,
onNavigate: setActiveIndex,
});
const typeahead = useTypeahead(context, {
listRef: labelRef,
activeIndex,
onMatch: setActiveIndex,
onTypingChange: setTyping,
});
const { getFloatingProps, getItemProps } = useInteractions([
listNavigation,
typeahead,
]);
const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
return (
<div ref={refs.setFloating} {...getFloatingProps()}>
{typing && <div>Typing...</div>}
{items.map((item, index) => (
<div
key={item}
ref={(el) => {
listRef.current[index] = el;
labelRef.current[index] = item;
}}
tabIndex={activeIndex === index ? 0 : -1}
style={{
background: activeIndex === index ? 'lightblue' : 'white',
padding: '8px',
}}
{...getItemProps()}
>
{item}
</div>
))}
</div>
);
}Context provider for managing lists of floating items with element and label references.
/**
* Context provider for managing floating lists
* @param props - List context configuration
* @returns Context provider for floating lists
*/
interface FloatingListProps {
children: React.ReactNode;
elementsRef: React.MutableRefObject<Array<HTMLElement | null>>;
labelsRef?: React.MutableRefObject<Array<string | null>>;
}
declare const FloatingList: React.FC<FloatingListProps>;Registers an item within a FloatingList context and provides ref management.
/**
* Registers item in floating list context
* @param props - List item configuration
* @returns Ref and index for the list item
*/
function useListItem(props?: UseListItemProps): {
ref: React.RefCallback<HTMLElement>;
index: number;
};
interface UseListItemProps {
label?: string | null;
}Usage Example:
import { FloatingList, useListItem } from '@floating-ui/react';
function ListWithItems() {
const elementsRef = useRef<Array<HTMLElement | null>>([]);
const labelsRef = useRef<Array<string | null>>([]);
return (
<FloatingList elementsRef={elementsRef} labelsRef={labelsRef}>
<Item label="First item" />
<Item label="Second item" />
<Item label="Third item" />
</FloatingList>
);
}
function Item({ label }: { label: string }) {
const { ref, index } = useListItem({ label });
return (
<div ref={ref} data-index={index}>
{label}
</div>
);
}Creates a single tab stop with arrow key navigation for composite widgets per WAI-ARIA guidelines.
/**
* Single tab stop with arrow key navigation for composite widgets
* @param props - Composite widget configuration
* @returns Composite widget container
*/
interface CompositeProps {
children: React.ReactNode;
role?: string;
orientation?: 'horizontal' | 'vertical' | 'both';
loop?: boolean;
cols?: number;
disabledIndices?: Array<number>;
activeIndex?: number;
onNavigate?: (activeIndex: number) => void;
itemSizes?: Array<number>;
dense?: boolean;
render?: React.ComponentPropsWithoutRef<any>;
}
declare const Composite: React.FC<CompositeProps>;Individual item within a Composite widget with proper focus management.
/**
* Individual item within Composite widget
* @param props - Composite item configuration
* @returns Composite item element
*/
interface CompositeItemProps {
children: React.ReactNode;
role?: string;
render?: React.ComponentPropsWithoutRef<any>;
}
declare const CompositeItem: React.FC<CompositeItemProps>;Usage Example:
import { Composite, CompositeItem } from '@floating-ui/react';
import { useState } from 'react';
function Toolbar() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<Composite
role="toolbar"
orientation="horizontal"
activeIndex={activeIndex}
onNavigate={setActiveIndex}
>
<CompositeItem role="button">
<button>Cut</button>
</CompositeItem>
<CompositeItem role="button">
<button>Copy</button>
</CompositeItem>
<CompositeItem role="button">
<button>Paste</button>
</CompositeItem>
</Composite>
);
}