useSelection

Multi-select hook with cmd-click toggle and shift-click range selection.

Install

npx shadcn@latest add https://substrate.georgedrury.co.uk/r/use-selection

Usage

import { useSelection } from "@/hooks/substrate/use-selection"

const itemIds = ["a", "b", "c", "d", "e"]

function LayerPanel() {
  const { selectedIds, select, selectAll, deselectAll, isSelected } =
    useSelection({
      items: itemIds,
      onSelectionChange: (ids) => console.log("Selected:", ids),
    })

  return (
    <ul>
      {itemIds.map((id) => (
        <li
          key={id}
          onClick={(e) => select(id, e)}
          style={{ background: isSelected(id) ? "var(--accent)" : undefined }}
        >
          {id}
        </li>
      ))}
    </ul>
  )
}

Features

The hook handles three interaction modes automatically based on modifier keys held during a click:

  • Plain click – selects only the clicked item, deselecting everything else.
  • ⌘/Ctrl + click – toggles the clicked item without affecting the rest of the selection.
  • Shift + click – selects a contiguous range from the last-clicked item to the current one. When combined with ⌘/Ctrl, the range is added to the existing selection rather than replacing it.

API

Options

OptionTypeDescription
itemsT[]All item IDs in display order – needed for shift-click range calculation
onSelectionChange(ids: Set<T>) => voidCalled whenever the selection changes

Returns

PropertyTypeDescription
selectedIdsSet<T>The current selection
select(id: T, event?) => voidHandle a click – reads metaKey, ctrlKey, shiftKey from the event
setSelection(ids: T[]) => voidReplace the selection with specific IDs
selectAll() => voidSelect every item
deselectAll() => voidClear the selection
isSelected(id: T) => booleanCheck whether an item is selected