useSelectionui-interaction

useSelection

NewSSR SAFE

Manages single, multi, and batch selection states with Set-based O(1) lookups and range selection.

$ pnpm dlx shadcn@latest add @byphongphan/use-selection
Requires @byphongphan in components.json
Loading preview...

Installation

$ pnpm dlx shadcn@latest add @byphongphan/use-selection
Requires @byphongphan in components.json

Usage

Table Batch Selection

import { useSelection } from "@/shared/hooks"import {  Table,  TableBody,  TableCell,  TableHead,  TableHeader,  TableRow,} from "@/registry/ui/table"import { Checkbox } from "@/registry/ui/checkbox"
interface User { id: string name: string email: string}
export function UserTable({ users }: { users: User[] }) { const { isSelected, isAllSelected, isPartiallySelected, toggle, toggleAll, selectedCount, } = useSelection(users, { getKey: (u) => u.id })
return ( <div className="space-y-3"> <div className="text-sm text-muted-foreground"> Selected: {selectedCount} of {users.length} </div>
<Table> <TableHeader> <TableRow> <TableHead className="w-10"> <Checkbox checked={isAllSelected} indeterminate={isPartiallySelected} onCheckedChange={toggleAll} aria-label="Select all" /> </TableHead> <TableHead>Name</TableHead> <TableHead>Email</TableHead> </TableRow> </TableHeader>
<TableBody> {users.map((user) => ( <TableRow key={user.id} data-state={isSelected(user.id) ? "selected" : undefined} > <TableCell className="w-10"> <Checkbox checked={isSelected(user.id)} onCheckedChange={() => toggle(user.id)} aria-label={`Select ${user.name}`} /> </TableCell> <TableCell>{user.name}</TableCell> <TableCell>{user.email}</TableCell> </TableRow> ))} </TableBody> </Table> </div> )}

Shift + Click Range Selection

import { useRef } from "react"import { useSelection, SelectionKey } from "@/shared/hooks"
export function SelectableList({ items }: { items: { id: string; label: string }[] }) { const lastSelectedKeyRef = useRef<SelectionKey | null>(null) const { isSelected, toggle, selectRange } = useSelection(items)
const handleItemClick = (key: string, e: React.MouseEvent) => { if (e.shiftKey && lastSelectedKeyRef.current) { selectRange(lastSelectedKeyRef.current, key) } else { toggle(key) lastSelectedKeyRef.current = key } }
return ( <ul className="divide-y border rounded-lg"> {items.map((item) => ( <li key={item.id} onClick={(e) => handleItemClick(item.id, e)} className={`p-3 cursor-pointer select-none ${ isSelected(item.id) ? "bg-primary/10" : "" }`} > {item.label} </li> ))} </ul> )}

Features

  • O(1) Set Performance: Uses native JavaScript Set<SelectionKey> internally for instant constant-time selection lookups.
  • Tri-State Header Checkbox: Accurately computes isAllSelected and isPartiallySelected for indeterminate state handling in tables and list headers.
  • Continuous Range Selection: Built-in selectRange(fromKey, toKey) allows implementing desktop-style Shift + Click multi-row selection in just one line.
  • Derived Selected Items: Automatically returns selectedItems array synchronized with selectedKeys.
  • Flexible Key Resolution: Defaults to item.id ?? item.key ?? item, or provide a custom getKey extractor for custom data models.

Parameters

ParameterTypeDefaultDescription
itemsreadonly T[]—The array of items available for selection.
options.getKey(item: T) => SelectionKey(item) => item.id ?? item.key ?? itemCustom function to extract a unique string or number identifier.
options.initialSelectedSelectionKey[] | Set<SelectionKey>—Initial keys selected on mount.

Returns

Returns a UseSelectionReturn<T> object:

PropertyTypeDescription
selectedKeysSet<SelectionKey>Active Set containing all selected identifiers.
selectedItemsT[]Array of full item objects corresponding to the selected keys.
selectedCountnumberNumber of currently selected items (selectedKeys.size).
isSelected(key: SelectionKey) => booleanChecks whether a specific item key is selected.
isAllSelectedbooleanWhether every item in the dataset is selected.
isPartiallySelectedbooleanWhether at least one item is selected, but not all (indeterminate state).
select(keys: SelectionKey | SelectionKey[]) => voidSelects one or multiple keys.
deselect(keys: SelectionKey | SelectionKey[]) => voidDeselects one or multiple keys.
toggle(key: SelectionKey) => voidInverts the selection status of a single key.
selectAll() => voidSelects every item in the dataset.
clear() => voidDeselects all items.
toggleAll() => voidToggles between selecting all and deselecting all items.
selectRange(fromKey: SelectionKey, toKey: SelectionKey) => voidSelects all items between fromKey and toKey inclusive (Shift-Click).
setSelectedKeysDispatch<SetStateAction<Set<SelectionKey>>>Direct state setter for manual overrides.

Command Palette

Quick search and commands navigation