or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-positioning.mdfocus-management.mdindex.mdinteraction-hooks.mdlayout-components.mdlist-navigation.mdpositioning-middleware.mdtransitions.mdtree-context.md
tile.json

list-navigation.mddocs/

List Navigation & Composite Widgets

System for creating accessible lists, menus, and composite widgets with keyboard navigation, virtual focus, type-ahead search, and proper ARIA support.

Capabilities

useListNavigation Hook

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

useTypeahead Hook

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

FloatingList Component

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>;

useListItem Hook

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

Composite Component

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>;

CompositeItem Component

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

Navigation Patterns

Menu Navigation

  • Vertical orientation for dropdown menus
  • Loop navigation for continuous scrolling
  • Focus on open for immediate keyboard access

Grid Navigation

  • Both orientation for 2D navigation
  • Cols property to define grid structure
  • Dense layout for optimized keyboard flow

Toolbar Navigation

  • Horizontal orientation for tool selection
  • Single tab stop pattern via Composite
  • Arrow key navigation between tools

Accessibility Features

ARIA Compliance

  • role="menu" for menu lists
  • role="menuitem" for menu items
  • role="toolbar" for composite toolbars
  • aria-activedescendant for virtual focus

Keyboard Support

  • Arrow keys: Navigate between items
  • Home/End: Jump to first/last item
  • Page Up/Down: Navigate by page (if supported)
  • Enter/Space: Activate focused item
  • Escape: Close list (if dismissible)

Focus Management

  • Virtual focus: Maintains single tab stop with aria-activedescendant
  • Real focus: Moves actual DOM focus between items
  • Focus restoration: Returns to appropriate element on close