Controlled State
Pass expandedIds / checkedIds / selectedIds explicitly whenever another part of your app needs to read or drive tree state.
This is the same controlled-component pattern React uses for form inputs: you own the state, TreeKit renders it and calls your onChange handler when the user interacts — it never updates your state for you. Reach for controlled state when you need to:
- Persist selection to a URL, form, or backend.
- Drive the tree from buttons elsewhere on the page ("Expand all", "Clear selection").
- Keep two trees, or a tree and a summary panel, in sync.
- Validate or transform a selection before it's applied.
Engineering
Frontend
Backend
Platform
Design
Marketing
Show code
const [expandedIds, setExpandedIds] = useState<string[]>(["engineering"]);
const [checkedIds, setCheckedIds] = useState<string[]>([]);
<TreeKit
data={data}
expandedIds={expandedIds}
onExpandedChange={setExpandedIds}
checkedIds={checkedIds}
onCheckedChange={setCheckedIds}
/>
<button onClick={() => setCheckedIds(allIds)}>Check all</button>Controlled means fully controlled
Once you pass
checkedIds, TreeKit will not check/uncheck anything on its own — every click calls onCheckedChange with the id set TreeKit computed, and it's up to you to call your setter. Forgetting to (e.g. passing onCheckedChange={() => {}}) will make checkboxes appear unresponsive — this is standard controlled-input behavior, not a bug.You can control any subset independently — e.g. controlled checkedIds with uncontrolled expansion (defaultExpandedIds only). See Uncontrolled State for the other end of this spectrum.