Vize

use-selection

Generic single- or multi-select state for lists, tables, and grids.

Package @vizejs/composable/use-selection
Own the source vize lib pull composable:use-selection
Runtime exports useSelection
Gzip budget 1280 B

Minimal setup

Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).

vp install @vizejs/composable

Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.

import { useSelection } from "@vizejs/composable/use-selection";

Try it

Choose workshop sessions with stable identities

Select up to two available sessions with native checkboxes, keeping the selected items accurate when catalogue objects change.

Open the interactive example

What to observe: Use Space on a checkbox, then choose a second session. The full workshop and further choices are disabled. Clear session choices restores an empty selection. Select available sessions fills only the two-place limit. Revise compiler title replaces all catalogue objects while keeping the selected stable ids and showing the updated title.

multiple: true makes selected an array; getKey gives each item a stable id. The composable's max and isSelectable constrain select/selectAll, and the example mirrors those constraints in native disabled states. selected follows catalogue order, while selectedKeys preserves selection order. clear() removes choices without changing catalogue data. Title revision is an explicit local immutable update, not a simulated server response. This synchronous selection state needs no browser globals or cleanup and renders the same empty state during SSR.

Compare the initial and interacted states
Choose workshop sessions with stable identities: initial state
Initial state
Choose workshop sessions with stable identities: state after the verified interactions
After the verified interactions

Copy the example

Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.

<script setup lang="ts">
import { ref, useId } from "vue";
import { useSelection } from "@vizejs/composable/use-selection";

const selectionId = useId();
const revised = ref(false);
const sessions = ref([
  { id: "compiler", title: "Compiler clinic", full: false },
  { id: "types", title: "Type checking clinic", full: false },
  { id: "ssr", title: "SSR workshop (full)", full: true },
  { id: "editor", title: "Editor tools clinic", full: false },
]);
const { selected, count, isSelected, toggle, selectAll, clear } = useSelection({
  items: sessions,
  multiple: true,
  max: 2,
  getKey: (item) => item.id,
  isSelectable: (item) => !item.full,
});

function reviseCompilerTitle(): void {
  sessions.value = sessions.value.map((item) => ({
    ...item,
    title: item.id === "compiler" ? "Compiler clinic (updated)" : item.title,
  }));
  revised.value = true;
}
</script>

<template>
  <div class="composable-example">
    <p>
      Choose up to two sessions from this local catalogue. The full workshop cannot be selected.
    </p>
    <fieldset>
      <legend>Workshop sessions</legend>
      <div v-for="item in sessions" :key="item.id">
        <input
          :id="`${selectionId}-${item.id}`"
          type="checkbox"
          class="session-checkbox"
          :checked="isSelected(item)"
          :disabled="item.full || (count >= 2 && !isSelected(item))"
          @change="() => toggle(item)"
        />
        <label :for="`${selectionId}-${item.id}`" class="session-label">{{ item.title }}</label>
      </div>
    </fieldset>
    <output aria-live="polite">{{ count }} of 2 places used</output>
    <p v-if="count === 2">Your two-session limit is reached. Clear a choice to select another.</p>
    <p v-if="count === 0">No sessions selected.</p>
    <ul v-else aria-label="Selected sessions">
      <li v-for="item in selected" :key="item.id">{{ item.title }}</li>
    </ul>
    <div class="example-actions">
      <button type="button" @click="selectAll">Select available sessions</button>
      <button type="button" :disabled="count === 0" @click="clear">Clear session choices</button>
      <button type="button" :disabled="revised" @click="reviseCompilerTitle">
        Revise compiler title
      </button>
    </div>
    <p>
      Revising the title replaces the catalogue's objects. Selected sessions keep their stable ids.
    </p>
  </div>
</template>

<style scoped>
.session-checkbox {
  inline-size: auto;
}
.session-label {
  display: inline;
}
</style>

Runtime contract

Utility Category Stability SSR Hydration Cleanup Targets Host globals Uses
useSelection state experimental safe stable none web, server, worker, native, desktop, terminal — —

API

useSelection

Generic single- or multi-select state for lists, tables, and grids. Keys (from getKey, the item itself by default) identify items, so the selection survives items being replaced. multiple: true switches the selected type to an array; max, required, and isSelectable constrain changes, and extendTo implements shift-click ranges from the anchor in display order. Synchronous state: SSR-safe and nothing to dispose.

function useSelection<Item, Key = Item, const Multiple extends boolean = false>( options: UseSelectionOptions<Item, Key, Multiple>, ): Selection<Item, Key, Multiple>
const rows = useSelection({ items: users, multiple: true, getKey: (user) => user.id });
rows.toggle(user);
rows.extendTo(other); // shift-click

useSelection

function useSelection( options: UseSelectionOptions<unknown, unknown, boolean>, ): Selection<unknown, unknown, boolean>

Types

UseSelectionOptions

Options for useSelection.

Member Type Description
items MaybeRefOrGetter<readonly Item[]> Selectable items, in display order. Reactive.
multiple? Multiple Allow several selected items.
getKey? (item: Item) => Key Stable identity of an item, for example its id. Keys survive item objects being replaced (refetches, immutable updates).
initial? readonly Key[] Keys selected initially.
max? number Maximum number of selected items in multiple mode; selecting beyond it is refused.
required? boolean Refuse to deselect the last selected item.
isSelectable? (item: Item) => boolean Veto selecting an item (for example disabled rows).

Selection

Selection state and commands returned by useSelection.

Member Type Description
selectedKeys Readonly<ShallowRef<readonly Key[]>> Selected keys in selection order.
selected ComputedRef<Multiple extends true ? Item[] : Item | undefined> Selected items in items order: an array in multiple mode, the single item (or undefined) otherwise. Keys whose item is currently absent are kept but not listed.
count ComputedRef<number> Number of selected keys.
isAllSelected ComputedRef<boolean> Whether every selectable item is selected (multiple mode).
isIndeterminate ComputedRef<boolean> Whether some but not all selectable items are selected (tri-state checkboxes).
anchor Readonly<ShallowRef<Key | undefined>> Item that anchors range selection (the last explicitly selected one).
isSelected (item: Item) => boolean Whether item is selected.
select (item: Item) => boolean Select item (replacing the selection in single mode).
deselect (item: Item) => boolean Deselect item.
toggle (item: Item) => boolean Toggle item.
selectOnly (item: Item) => boolean Select exactly item.
extendTo (item: Item) => boolean Select every item between the anchor and item inclusive (shift-click); in single mode this selects item.
selectAll () => void Select every selectable item, up to max (multiple mode).
clear () => void Clear the selection (ignores required).