Skip to content
On this page

useCollection

useCollection creates a collection manager for keyboard navigation. Use it when your floating surface (menu, submenu, combobox, or dropdown) needs to manage active focus across a list of items.

Type

ts
function useCollection(options: UseCollectionOptions): UseCollectionReturn;

interface UseCollectionOptions {
  values: MaybeRefOrGetter<readonly string[]>;
  isValueDisabled?: (value: string) => boolean;
}

interface CollectionNavigationOptions {
  loop?: boolean;
}

interface UseCollectionReturn {
  activeValue: Ref<string | null>;
  setActiveValue: (value: string | null) => void;
  setNext: (options?: CollectionNavigationOptions) => void;
  setPrevious: (options?: CollectionNavigationOptions) => void;
  setFirst: () => void;
  setLast: () => void;
  isItemDisabled: (value: string) => boolean;
}

Details

useCollection satisfies the NavigableCollection shape expected by useListNavigation.

  • values is the ordered set of item values.
  • Values must be stable strings.
  • isValueDisabled marks values that navigation should skip.
  • setActiveValue() ignores unknown and disabled values.
  • setNext() and setPrevious() move through enabled values only.
  • Pass { loop: true } to wrap at the collection boundaries.
  • If the active value disappears from values or becomes disabled, it is cleared.

For nested multi-level menus, each menu and submenu level creates its own useCollection and coordinates via useFloatingContext (parentContext).

Example

This menu uses useCollection for flat keyboard navigation.

vue
<script setup lang="ts">
import { computed, ref } from "vue";
import {
  useClick,
  useCollection,
  useFloatingContext,
  useListNavigation,
  usePosition,
} from "v-float";

const anchorEl = ref<HTMLElement | null>(null);
const floatingEl = ref<HTMLElement | null>(null);

const items = [
  { value: "edit", label: "Edit" },
  { value: "duplicate", label: "Duplicate" },
  { value: "archive", label: "Archive", disabled: true },
];

const values = computed(() => items.map((item) => item.value));
const collection = useCollection({
  values,
  isValueDisabled: (value) => value === "archive",
});

const context = useFloatingContext({ anchorEl, floatingEl });
const { styles } = usePosition(context);

useClick(context);
useListNavigation(context, {
  collection,
  loop: true,
});
</script>

<template>
  <button ref="anchorEl" type="button">Actions</button>

  <div v-if="context.state.open.value" ref="floatingEl" role="menu" :style="styles">
    <button
      v-for="item in items"
      :key="item.value"
      type="button"
      role="menuitem"
      :disabled="collection.isItemDisabled(item.value)"
      :class="{ active: collection.activeValue.value === item.value }"
    >
      {{ item.label }}
    </button>
  </div>
</template>

See Also

useCollection has loaded