or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

class-component.mdcombobox.mdindex.mdmultiple-selection.mdselect.md
tile.json

select.mddocs/

Select Components

The useSelect hook provides functionality for building accessible select/dropdown components with full keyboard navigation and ARIA compliance. It's ideal for single-selection scenarios where users choose from a list of options.

Capabilities

useSelect Hook

Creates a select component with full accessibility features and keyboard navigation.

/**
 * Hook for building accessible select/dropdown components
 * @param props - Configuration options for the select component
 * @returns Object containing state, actions, and prop getters
 */
function useSelect<Item>(props: UseSelectProps<Item>): UseSelectReturnValue<Item>;

interface UseSelectProps<Item> {
  /** Array of items to display in the select dropdown */
  items: Item[];
  /** Function to convert an item to its string representation */
  itemToString?: (item: Item | null) => string;
  /** Function to generate a unique key for each item */
  itemToKey?: (item: Item | null) => any;
  /** Function to determine if an item is disabled */
  isItemDisabled?: (item: Item, index: number) => boolean;
  /** Currently highlighted item index */
  highlightedIndex?: number;
  /** Initial highlighted index when component mounts */
  initialHighlightedIndex?: number;
  /** Default highlighted index for uncontrolled usage */
  defaultHighlightedIndex?: number;
  /** Whether the dropdown is open */
  isOpen?: boolean;
  /** Initial open state when component mounts */
  initialIsOpen?: boolean;
  /** Default open state for uncontrolled usage */
  defaultIsOpen?: boolean;
  /** Currently selected item */
  selectedItem?: Item | null;
  /** Initial selected item when component mounts */
  initialSelectedItem?: Item | null;
  /** Default selected item for uncontrolled usage */
  defaultSelectedItem?: Item | null;
  /** Custom ID for the component */
  id?: string;
  /** ID for the label element */
  labelId?: string;
  /** ID for the menu element */
  menuId?: string;
  /** ID for the toggle button element */
  toggleButtonId?: string;
  /** Function to generate IDs for menu items */
  getItemId?: (index: number) => string;
  /** Custom function to handle scrolling items into view */
  scrollIntoView?: (node: HTMLElement, menuNode: HTMLElement) => void;
  /** Custom state reducer for advanced state management */
  stateReducer?: (
    state: UseSelectState<Item>,
    actionAndChanges: UseSelectStateChangeOptions<Item>
  ) => Partial<UseSelectState<Item>>;
  /** Callback when selected item changes */
  onSelectedItemChange?: (changes: UseSelectSelectedItemChange<Item>) => void;
  /** Callback when open state changes */
  onIsOpenChange?: (changes: UseSelectIsOpenChange<Item>) => void;
  /** Callback when highlighted index changes */
  onHighlightedIndexChange?: (changes: UseSelectHighlightedIndexChange<Item>) => void;
  /** Callback when any state changes */
  onStateChange?: (changes: UseSelectStateChange<Item>) => void;
  /** Function to generate accessibility status messages */
  getA11yStatusMessage?: (options: UseSelectState<Item>) => string;
  /** Environment object for SSR/testing scenarios */
  environment?: Environment;
}

interface UseSelectReturnValue<Item> {
  /** Current highlighted item index */  
  highlightedIndex: number;
  /** Currently selected item */
  selectedItem: Item | null;
  /** Whether the dropdown menu is open */
  isOpen: boolean;
  /** Current input value (always empty string for select) */
  inputValue: string;
  
  /** Actions for controlling the select programmatically */
  reset: () => void;
  openMenu: () => void;
  closeMenu: () => void;
  toggleMenu: () => void;
  selectItem: (item: Item | null) => void;
  setHighlightedIndex: (index: number) => void;
  
  /** Prop getters for UI elements */
  getToggleButtonProps: <Options>(
    options?: UseSelectGetToggleButtonPropsOptions & Options,
    otherOptions?: GetPropsCommonOptions
  ) => UseSelectGetToggleButtonReturnValue;
  getLabelProps: <Options>(
    options?: UseSelectGetLabelPropsOptions & Options
  ) => UseSelectGetLabelPropsReturnValue;
  getMenuProps: <Options>(
    options?: UseSelectGetMenuPropsOptions & Options,
    otherOptions?: GetPropsCommonOptions
  ) => UseSelectGetMenuReturnValue;
  getItemProps: <Options>(
    options: UseSelectGetItemPropsOptions<Item> & Options
  ) => UseSelectGetItemPropsReturnValue;
}

Usage Examples:

import { useSelect } from 'downshift';

// Basic select example
function BasicSelect() {
  const items = ['Apple', 'Banana', 'Cherry'];
  const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps } = useSelect({
    items,
  });

  return (
    <div>
      <label {...getLabelProps()}>Choose a fruit:</label>
      <button {...getToggleButtonProps()}>
        {selectedItem || 'Select item'}
      </button>
      <ul {...getMenuProps()}>
        {isOpen &&
          items.map((item, index) => (
            <li
              style={highlightedIndex === index ? { backgroundColor: '#bde4ff' } : {}}
              key={`${item}${index}`}
              {...getItemProps({ item, index })}
            >
              {item}
            </li>
          ))}
      </ul>
    </div>
  );
}

// Advanced select with custom itemToString and state reducer
function AdvancedSelect() {
  const items = [
    { id: 1, name: 'Apple', category: 'fruit' },
    { id: 2, name: 'Carrot', category: 'vegetable' },
    { id: 3, name: 'Banana', category: 'fruit' },
  ];

  const stateReducer = (state, actionAndChanges) => {
    const { changes, type } = actionAndChanges;
    switch (type) {
      case useSelect.stateChangeTypes.ToggleButtonKeyDownCharacter:
        return {
          ...changes,
          // Custom logic for character navigation
        };
      default:
        return changes;
    }
  };

  const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps } = useSelect({
    items,
    itemToString: (item) => (item ? item.name : ''),
    stateReducer,
    onSelectedItemChange: ({ selectedItem }) => {
      console.log('Selected:', selectedItem);
    },
  });

  return (
    <div>
      <label {...getLabelProps()}>Choose an item:</label>
      <button {...getToggleButtonProps()}>
        {selectedItem ? selectedItem.name : 'Select item'}
      </button>
      <ul {...getMenuProps()}>
        {isOpen &&
          items.map((item, index) => (
            <li
              style={highlightedIndex === index ? { backgroundColor: '#bde4ff' } : {}}
              key={item.id}
              {...getItemProps({ item, index })}
            >
              {item.name} ({item.category})
            </li>
          ))}
      </ul>
    </div>
  );
}

State and Actions

The hook returns current state and action functions for programmatic control.

interface UseSelectState<Item> {
  /** Index of currently highlighted item (-1 if none) */
  highlightedIndex: number;
  /** Currently selected item */
  selectedItem: Item | null;
  /** Whether dropdown menu is open */
  isOpen: boolean;
  /** Input value (always empty string for select) */
  inputValue: string;
}

interface UseSelectActions<Item> {
  /** Reset to initial state */
  reset: () => void;
  /** Open the dropdown menu */
  openMenu: () => void;
  /** Close the dropdown menu */
  closeMenu: () => void;
  /** Toggle the dropdown menu open/closed */
  toggleMenu: () => void;
  /** Select a specific item */
  selectItem: (item: Item | null) => void;
  /** Set the highlighted index */
  setHighlightedIndex: (index: number) => void;
}

Prop Getters

Prop getters return props to spread onto DOM elements with proper event handlers and accessibility attributes.

interface UseSelectGetToggleButtonPropsOptions extends React.HTMLProps<HTMLElement> {
  /** Custom ref key (defaults to 'ref') */
  refKey?: string;
  /** Event handler for React Native press events */
  onPress?: (event: React.BaseSyntheticEvent) => void;
}

interface UseSelectGetToggleButtonReturnValue {
  'aria-activedescendant': string;
  'aria-controls': string;
  'aria-expanded': boolean;
  'aria-haspopup': 'listbox';
  'aria-labelledby': string | undefined;
  id: string;
  role: 'combobox';
  tabIndex: 0;
  ref?: React.RefObject<any>;
  onBlur?: React.FocusEventHandler;
  onClick?: React.MouseEventHandler;
  onPress?: (event: React.BaseSyntheticEvent) => void;
  onKeyDown?: React.KeyboardEventHandler;
}

interface UseSelectGetMenuReturnValue {
  'aria-labelledby': string | undefined;
  role: 'listbox';
  id: string;
  ref?: React.RefObject<any>;
  onMouseLeave: React.MouseEventHandler;
}

interface UseSelectGetItemPropsOptions<Item> {
  /** The item this props object is for */
  item: Item;
  /** Index of the item in the items array */
  index?: number;
  /** Custom ref key (defaults to 'ref') */
  refKey?: string;
}

interface UseSelectGetItemPropsReturnValue {
  'aria-disabled': boolean;
  'aria-selected': boolean;
  id: string;
  role: 'option';
  ref?: React.RefObject<any>;
  onClick?: React.MouseEventHandler;
  onMouseDown?: React.MouseEventHandler;
  onMouseMove?: React.MouseEventHandler;
  onPress?: React.MouseEventHandler;
}

State Change Types

Constants for identifying different types of state changes in the state reducer.

enum UseSelectStateChangeTypes {
  ToggleButtonClick = '__togglebutton_click__',
  ToggleButtonKeyDownArrowDown = '__togglebutton_keydown_arrow_down__',
  ToggleButtonKeyDownArrowUp = '__togglebutton_keydown_arrow_up__',
  ToggleButtonKeyDownCharacter = '__togglebutton_keydown_character__',
  ToggleButtonKeyDownEscape = '__togglebutton_keydown_escape__',
  ToggleButtonKeyDownHome = '__togglebutton_keydown_home__',
  ToggleButtonKeyDownEnd = '__togglebutton_keydown_end__',
  ToggleButtonKeyDownEnter = '__togglebutton_keydown_enter__',
  ToggleButtonKeyDownSpaceButton = '__togglebutton_keydown_space_button__',
  ToggleButtonKeyDownPageUp = '__togglebutton_keydown_page_up__',
  ToggleButtonKeyDownPageDown = '__togglebutton_keydown_page_down__',
  ToggleButtonBlur = '__togglebutton_blur__',
  MenuMouseLeave = '__menu_mouse_leave__',
  ItemMouseMove = '__item_mouse_move__',
  ItemClick = '__item_click__',
  FunctionToggleMenu = '__function_toggle_menu__',
  FunctionOpenMenu = '__function_open_menu__',
  FunctionCloseMenu = '__function_close_menu__',
  FunctionSetHighlightedIndex = '__function_set_highlighted_index__',
  FunctionSelectItem = '__function_select_item__',
  FunctionSetInputValue = '__function_set_input_value__',
  FunctionReset = '__function_reset__',
}

Access via useSelect.stateChangeTypes:

import { useSelect } from 'downshift';

const stateReducer = (state, actionAndChanges) => {
  switch (actionAndChanges.type) {
    case useSelect.stateChangeTypes.ToggleButtonClick:
      // Handle toggle button click
      return actionAndChanges.changes;
    default:
      return actionAndChanges.changes;
  }
};