Skip to content

feat(react-tree): add design-free base hooks for Tree and TreeItemLayout - #36932

Open
Dmytro Kirpa (dmytrokirpa) wants to merge 14 commits into
microsoft:masterfrom
dmytrokirpa:claude/sharp-meitner-y7rifz-tree-base
Open

Dmytro Kirpa (dmytrokirpa) wants to merge 14 commits into
microsoft:masterfrom
dmytrokirpa:claude/sharp-meitner-y7rifz-tree-base

Conversation

@dmytrokirpa

@dmytrokirpa Dmytro Kirpa (dmytrokirpa) commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Previous Behavior

Tree behavior and TreeItemLayout state were coupled to Fluent design defaults, Fluent selector components, collapse motion, and Tabster focus management, preventing reuse as design-free primitives.

New Behavior

Adds useTreeBase_unstable and useTreeItemLayoutBase_unstable with base props/state types. The base Tree uses plain DOM focus tracking and navigation, omits design-only motion, and removes closed subtrees; the base layout uses native checkbox/radio inputs and leaves expand-icon content empty. Existing styled components retain their Fluent defaults and Tabster behavior. Includes tests for both base and styled behavior.

Related Issue(s)

Dmytro Kirpa (dmytrokirpa) and others added 11 commits April 4, 2026 20:41
Pin the styled behavior that the upcoming base hooks extraction must preserve:
roving tab index, arrow/Home/End/type-ahead navigation, branch open/close,
closed subtrees not rendering, the Collapse motion on subtrees, design
defaults, the Fluent Checkbox/Radio selector, the chevron expand icon,
Tabster arrow navigation on actions and the small button context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M9WfDGcoZcv2FA37mMYqmP
Add useTreeBase_unstable and useTreeItemLayoutBase_unstable, free of Tabster,
motion and Fluent design components, so they can back a headless Tree.

- Tree: the base hook drops appearance/size and the Collapse motion, drives the
  roving tab index from plain DOM focus events and finds focusable actions
  with a DOM query. Closed subtrees are not rendered without a collapse motion.
  useTree_unstable wraps it, re-adding Tabster focus handling, the Collapse
  motion and the design props.
- TreeItemLayout: the base hook renders a native checkbox/radio selector, no
  default expand icon content, no Tabster arrow navigation on the actions and
  no button context. useTreeItemLayout_unstable layers those back on.
- useTreeContextValues_unstable, renderTree_unstable and
  renderTreeItemLayout_unstable accept the base state.
- Export the base types and the TreeItemLayout action slot types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M9WfDGcoZcv2FA37mMYqmP
@dmytrokirpa Dmytro Kirpa (dmytrokirpa) changed the title Claude/sharp meitner y7rifz tree base feat(react-tree): add design-free base hooks for Tree and TreeItemLayout Oct 19, 2026
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-components
react-components: all base hooks
219.615 kB
68.539 kB
233.188 kB
72.422 kB
13.573 kB
3.883 kB
react-components
react-components: entire library
1.283 MB
322.23 kB
1.285 MB
322.785 kB
1.951 kB
555 B
react-tree
FlatTree
132.701 kB
39.468 kB
133.869 kB
39.792 kB
1.168 kB
324 B
react-tree
PersonaFlatTree
134.558 kB
39.973 kB
135.656 kB
40.203 kB
1.098 kB
230 B
react-tree
PersonaTree
130.645 kB
38.769 kB
132.158 kB
38.877 kB
1.513 kB
108 B
react-tree
Tree
128.788 kB
38.287 kB
130.368 kB
38.484 kB
1.58 kB
197 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Button, FluentProvider & webLightTheme
68.214 kB
19.863 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
223.278 kB
67.379 kB
react-components
react-components: FluentProvider & webLightTheme
41.444 kB
13.893 kB
react-portal-compat
PortalCompatProvider
5.198 kB
2.104 kB
react-timepicker-compat
TimePicker
139.754 kB
45.561 kB
🤖 This report was generated against 7e6920d517fe2d7160cc49bb1db5aee5f0e84d00

@github-actions

Copy link
Copy Markdown

Pull request demo site: URL

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved rendering, selector synchronization, keyboard navigation, and runtime-isolation defects need correction before approval.

5 open issues
What changed in this PR

Adds reusable base hooks for Tree and TreeItemLayout to support the upcoming headless Tree components while preserving styled adapters.

Changes:

  • Separates base behavior from Fluent defaults, motion, and focus management.
  • Adds base and styled behavior tests.
  • Updates exports, API documentation, bundle tracking, and release metadata.
File Description
packages/​react-components/​react-tree/​library/​src/​TreeItemLayout.ts Exports base layout APIs.
packages/​react-components/​react-tree/​library/​src/​Tree.ts Exports base Tree APIs.
packages/​react-components/​react-tree/​library/​src/​index.ts Exposes new package APIs.
packages/​react-components/​react-tree/​library/​src/​hooks/​useTreeNavigationBase.ts Adds shared navigation and DOM focus lookup.
packages/​react-components/​react-tree/​library/​src/​hooks/​useTreeNavigation.ts Wraps shared navigation with Tabster.
packages/​react-components/​react-tree/​library/​src/​hooks/​useSubtree.ts Removes motion from base subtree state.
packages/​react-components/​react-tree/​library/​src/​hooks/​useRovingTabIndexes.ts Delegates to shared roving focus logic.
packages/​react-components/​react-tree/​library/​src/​hooks/​useRovingTabIndexBase.ts Adds injectable focus tracking.
packages/​react-components/​react-tree/​library/​src/​hooks/​useRootTree.ts Removes design defaults from base state.
packages/​react-components/​react-tree/​library/​src/​components/​TreeItemLayout/​useTreeItemLayoutBase.test.tsx Tests native selectors and base defaults.
packages/​react-components/​react-tree/​library/​src/​components/​TreeItemLayout/​useTreeItemLayout.tsx Separates base layout and styled defaults.
packages/​react-components/​react-tree/​library/​src/​components/​TreeItemLayout/​useTreeItemLayout.test.tsx Tests styled layout defaults.
packages/​react-components/​react-tree/​library/​src/​components/​TreeItemLayout/​TreeItemLayout.types.ts Defines base layout types.
packages/​react-components/​react-tree/​library/​src/​components/​TreeItemLayout/​renderTreeItemLayout.tsx Supports rendering base layout state.
packages/​react-components/​react-tree/​library/​src/​components/​TreeItemLayout/​index.ts Exports base layout definitions.
packages/​react-components/​react-tree/​library/​src/​components/​Tree/​useTreeContextValues.ts Accepts base state in context creation.
packages/​react-components/​react-tree/​library/​src/​components/​Tree/​useTreeBase.test.tsx Tests base rendering and navigation.
packages/​react-components/​react-tree/​library/​src/​components/​Tree/​useTree.ts Separates base and styled Tree hooks.
packages/​react-components/​react-tree/​library/​src/​components/​Tree/​useTree.test.tsx Tests styled Tree behavior.
packages/​react-components/​react-tree/​library/​src/​components/​Tree/​Tree.types.ts Defines base Tree types.
packages/​react-components/​react-tree/​library/​src/​components/​Tree/​renderTree.tsx Omits closed subtrees without motion.
packages/​react-components/​react-tree/​library/​src/​components/​Tree/​index.ts Exports base Tree definitions.
packages/​react-components/​react-tree/​library/​src/​components/​FlatTree/​useFlatTree.ts Restores styled defaults around shared hooks.
packages/​react-components/​react-tree/​library/​etc/​react-tree.api.md Records public API changes.
packages/​react-components/​react-components/​bundle-size/​BaseHooks.fixture.js Includes Tree base hooks in bundle tracking.
change/​@fluentui-react-tree-7b07ea30-d0c0-4b49-835e-3596a8145d17.json Adds minor-release metadata.

Review effort: Balanced

Comment thread packages/react-components/react-tree/library/src/hooks/useTreeNavigationBase.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Shared focus and native-input changes need browser-level validation across base, styled, and flat trees after the unresolved issues are addressed.

2 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Track focus changes in the capture phase

packages/​react-components/​react-tree/​library/​src/​hooks/​useRovingTabIndexBase.ts:44

This bubbling document listener misses focus changes when a consumer stops propagation, for example with onFocus={event => event.stopPropagation()} on the base Tree. Programmatically focusing the third item then leaves the first item at tabIndex=0 and the focused item at -1, so re-entering the tree returns to the wrong item. Subscribe in the capture phase so tracking runs before consumer bubbling handlers, and use the same option for cleanup.

Medium severity Use a stable marker to identify the actions slot

packages/​react-components/​react-tree/​library/​src/​hooks/​useTreeNavigationBase.ts:179

The base layout's actions role is consumer-overridable: slot.optional lets actions.role replace the default toolbar role. With actions={{ role: 'group', visible: true, ... }}, an unstyled layout matches neither selector here. In treegrid mode ArrowRight cannot enter its actions, and ArrowLeft from an action cannot return to the same item through the actions-navigation branch. Identify the actions slot with a stable internal marker or registered element reference rather than its customizable ARIA role.

5 resolved since last review

Review effort: Balanced

- Reference count a single keyboard modality tracker per document instead
  of installing document listeners for every base TreeItemLayout with actions
- Stop propagating actions.visible and actions.onVisibilityChange to the DOM
- Set inert through a ref in the treegrid test, as React 17 and 18 drop it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
useTree_unstable called useFocusFinders for every nested subtree even though
only the root tree drives navigation. Pass the navigation options as a hook
that only the root branch invokes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0128HHjJ1oVRApVhfbatcMe1

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants