esc
navigate select
Browse documentation

Selection

TreeKit tracks two independent kinds of selection: checkbox state and the 'active' or highlighted row.

Two kinds of selection

It's tempting to conflate "the checked boxes" with "the row the user clicked," but they're genuinely different concerns — a file explorer highlights the row you clicked without checking anything; a permissions tree checks boxes without any notion of a currently "active" row. TreeKit keeps them as separate, independently controllable state:

  • checkedIds — the checkbox state, with cascading rules (this page).
  • selectedIds — the highlighted/active row(s), governed by selectionMode ("none" | "single" | "multiple"). Unrelated to checkboxes.
Engineering
Frontend
React
Vue
Svelte

1 node checked

Show code
<TreeKit
  data={data}
  checkedIds={checkedIds}
  onCheckedChange={setCheckedIds}
/>

Notice you can click anywhere on "React" or "Vue" above, not just their small checkboxes — checkOnClick (default true) extends the checkbox's click target to the whole row for leaf nodes. Parent rows use that same click for expand/collapse instead (see Expansion); set checkOnClick={false} if you'd rather restrict checking to the checkbox itself.

Cascading with selectionPropagation

By default, checking a parent checks every descendant, and a parent shows indeterminate when only some of its descendants are checked. Both directions are independently configurable via selectionPropagation:

PropTypeDefaultDescription
toChildrenbooleantrueChecking/unchecking a node applies the same state to every descendant.
toParentsbooleantrueA parent's checked state is derived from its children (checked / unchecked / indeterminate) instead of tracked independently.
Engineering
Frontend
React
Vue
Svelte
Show code
<TreeKit
  data={data}
  selectionPropagation={{ toChildren: true, toParents: false }}
/>

Four combinations, four real use cases:

  • Both true (default, "cascade") — classic permission/category trees.
  • toChildren only — checking a category checks its items, but checking every item individually doesn't retroactively check the category.
  • toParents only — users check leaves directly; parents reflect aggregate state but clicking a parent doesn't bulk-check its children.
  • Neither ("strict") — every node's checkbox is fully independent, exactly like a flat list of checkboxes that happens to be visually nested.

How indeterminate is computed

Indeterminate state is derived, never stored. TreeKit keeps only the set of explicitly-checked leaf ids; on every render it walks the tree once (children before parents) to compute each node's tri-state value. See Indeterminate State for the exact algorithm, and Utilities for the exported computeCheckedStates function if you want the same math outside React.

Reading selection back

Beyond the id arrays your callbacks receive, TreeKit's headless useTree hook exposes getCheckedNodes(), getIndeterminateNodes(), and getSelectedNodes() — convenient when you want full node objects (with your data field) rather than bare ids.

import { useTree } from "@treekit-ui/core";

const tree = useTree({ data, checkedIds, onCheckedChange: setCheckedIds });
const checkedNodes = tree.getCheckedNodes(); // TreeNode<T>[]