docs/solutions/logic-errors/2026-04-06-toc-elements-must-reuse-content-controller-for-active-heading-state.md
The TOC package already had a real active-section source of truth:
useContentController.
But the live TOC element hook, useTocElementState, did not use it. That left
the package in a stupid split state:
The app papered over that gap badly by rendering aria-current on every TOC
row.
useTocElementState when
useContentController already owned scroll and navigation
flash behaviorRoute useTocElementState through useContentController and expose
activeContentId from the element-state hook.
That keeps one real source of truth for:
Then make the live TOC node render exactly one current row:
aria-current="location" only for the active headingThe content controller already owns the only state that matters here: "which heading is active for the current document position?"
Once the element hook reuses that controller instead of duplicating a smaller click-only helper, the package and app stop drifting apart.
That gives consumers one coherent shape:
useTocElementState for headings, current section, and scroll handoffuseTocElement for click wiringNo extra observer logic in the app. No second navigation state. No bogus accessibility attributes.
TOC activation is navigation-only. Generated TOC entries should not synthesize block-selection state or place a landed caret in the target heading as a side effect of navigation.
These checks passed:
bun test packages/toc/src/react/hooks/useTocElement.spec.tsx packages/toc/src/react/hooks/useContentController.spec.tsx packages/toc/src/react/hooks/useTocSideBar.spec.tsx packages/toc/src/lib/BaseTocPlugin.spec.ts packages/toc/src/lib/transforms/insertToc.spec.ts apps/www/src/registry/ui/toc-node.spec.tsx
pnpm install
pnpm turbo build --filter=./packages/toc
pnpm turbo typecheck --filter=./packages/toc
pnpm --filter www build:registry
pnpm lint:fix
Browser verification used browser-use against a clean www dev server on
127.0.0.1:3001. The standalone TOC block route loaded, but the docs preview
surface stayed stuck on Loading..., so browser proof for active-row promotion
was limited to route-level sanity rather than a full interactive TOC assertion.
aria-current as a static boolean on repeated navigation rows