Repository navigation
Components: Complete the Fabric FocusZone for macOS and Windows #4217
Description
Summary
Finish and integrate the existing Fabric FocusZone work for macOS and React Native Windows so keyboard-oriented container components have a supported roving-focus foundation. The implementation must preserve the Win32 Paper path unchanged; the remaining work is integration and platform validation rather than a new design.
Observed. Substantial work already exists on
origin/user/jasonvmo/fabric-focus-zone
(ea2a704):
a macOS Fabric component view, a complete React Native Windows Fabric C++/WinRT
implementation with a generated codegen base, a react-native.config.cjs for
autolinking, a rewritten JavaScript wrapper on framework-base, and a SPEC.md. This
task is about finishing, validating, and integrating that work rather than starting
it.
Goal
Land a FocusZone that works on macOS Fabric and on React Native Windows Fabric, keep
the Win32 Paper path working unchanged, and give packages/agentic/components a
decided, documented way to obtain roving keyboard focus for its container components.
Stage
Stage 1 (beta foundations). Wave 3 of the missing components cannot be generated
faithfully until the focus decision in this task is made.
Why it matters
Observed. packages/components/FocusZone today has a macOS Paper native
implementation and no Windows implementation at all. Its codegen spec is
codegenNativeComponent<NativeProps>('RCTFocusZone') with no Fabric name mapping, so
the name it registers is the Paper name.
Observed. Five legacy components depend on it - ContextualMenu, Menu
(MenuList, MenuGroup), RadioGroup, and TabList - so on React Native Windows
these components have no focus container.
Observed. The agentic library does not use FocusZone at all, and four staged
component specs (toolbar, tablist, radio-group, menu) record the absence of a
focus/roving-tabindex contract inside that package as a blocking reason.
Inferred. Without this, the whole focus-orchestrated family of the canonical
catalog - toolbar, tablist, radio-group, nav, simple-nav, menu, list,
dropdown, combobox - is either not implementable or is implementable only with an
ad-hoc per-component focus loop, which is exactly the duplication the package's
optimization guidance forbids.
Observed current state
Working tree
Observed. packages/components/FocusZone
is @fluentui-react-native/focus-zone 0.23.2. It depends on
@uifabricshared/foundation-composable, @uifabricshared/foundation-settings,
@fluentui-react-native/adapters, and @fluentui-react-native/interactive-hooks, and
declares furn.jestPlatform: "win32".
| Path | State |
|---|---|
macos/RCTFocusZone.h / .m, RCTFocusZoneManager.h / .m |
macOS Paper RCTView subclass and RCTViewManager - the only native implementation present |
RCTFocusZone.podspec |
s.dependency 'React' (old-architecture podspec style), macOS deployment target 10.15 |
src/FocusZone.ts |
Old composable() wrapper |
src/FocusZoneNativeComponent.ts |
codegenNativeComponent<NativeProps>('RCTFocusZone'), no paperComponentName option |
src/FocusZone.types.ts |
Public props: componentRef, defaultTabbableElement, focusZoneDirection, disabled, isCircularNavigation, use2DNavigation, tabKeyNavigation, onFocus, navigationOrderInRenderOrder |
windows/ |
Does not exist |
SPEC.md |
Does not exist in the working tree |
src/__tests__/FocusZone.test.tsx |
Jest snapshot tests, run under the win32 preset |
Observed. packages/components/FocusTrapZone shares the codegen-spec and
no-windows/ shape, but it has no checked-in macOS native implementation or
podspec in this repository. FocusZone would still be the first package in the
working tree to carry a Fabric Windows native project.
Observed. Win32 has no .win32.* file in the package. @office-iss/react-native-win32
is an optional peer dependency and the platform supplies its own native FocusZone,
reached through the RCTFocusZone component name.
Existing end-to-end and manual coverage
Observed. apps/E2E/src/FocusZone contains
Windows/Win32 directional and tab/circular-navigation specs plus a thin macOS spec.
apps/tester-core/src/TestComponents/FocusZone renders the manual configurations, and
apps/fluent-tester depends on the package directly.
The Fabric branch
Observed, origin/user/jasonvmo/fabric-focus-zone @
ea2a704,
commits ffd510a -> 537a7a8:
| Added or changed | What it does |
|---|---|
macos/RCTFocusZoneComponentView.h / .mm |
RCTViewComponentView subclass under RCT_NEW_ARCH_ENABLED that hosts the existing Paper RCTFocusZone as its content view and forwards props, first-responder handling, and keyDown:; registers the component name FocusZone |
windows/FRNFocusZone/** |
Full C++/WinRT Fabric implementation: FocusZoneComponentView.cpp/.h (key handling, tab modes, linear and 2D focus movement, focus restoration, incoming-focus redirection), ReactPackageProvider, .vcxproj with RnwNewArchOnly, pre-generated codegen/.../FocusZone.g.h |
react-native.config.cjs |
Declares the Windows source directory and project for autolinking |
package.json |
Adds codegenConfig with a windows generator block and ios.componentProvider mapping FocusZone -> RCTFocusZoneComponentView; swaps the composable/adapters dependencies for @fluentui-react-native/framework-base |
src/FocusZoneNativeComponent.ts |
codegenNativeComponent<NativeProps>('FocusZone', { paperComponentName: 'RCTFocusZone' }) - the Fabric/Paper name split that keeps Win32 working |
src/FocusZone.tsx |
Replaces composable() with phasedComponent / directComponent / mergeProps |
src/FocusZone.stories.tsx, src/__tests__/FocusZone.types.test.ts, SPEC.md |
New story, type test, and spec |
RCTFocusZone.podspec |
Switched to install_modules_dependencies(s) |
Observed. The branch also integrates the consuming Storybook application, at its
pre-repath location packages/agentic-components/storybook:
| Change | What it does |
|---|---|
src/main.ts |
Adds ../../../components/FocusZone/src/**/*.stories.?(ts|tsx) to the story globs, so the new FocusZone.stories.tsx is discovered |
scripts/smoke-stories.json |
Adds a fourth render case, primitives-focuszone--two-dimensional-navigation, with testID focus-zone-item-1 |
windows-tests/storybook-smoke.test.cjs |
Adds two WinAppDriver focus tests: geometric 2D navigation (Down from item 1 lands on item 4) and linear navigation with Tab exit (Right, Down, then Tab to the element after the zone) |
agent-map.yaml, scripts/storybook-agent.cjs, index.js, AGENTS.md, README.md, package.json |
New agent-facing map and orchestration entry point; package.json also restores windows to the direct run-windows invocation |
Observed. @fluentui-react-native/focus-zone is already a workspace:* dependency
of the Storybook app on both origin/main
(apps/storybook/package.json) and the branch,
so the package linkage itself is not a branch-only change. The Windows native project
reaches a generated solution through the package's react-native.config.cjs
autolinking rather than through an edit to the app.
Observed gaps on that branch:
codegenConfig.includesGeneratedCodeistrueon a package that also builds for
macOS.windows-fabric-native-components.md
states that flag should be set only on a Windows-only package because it removes
the generatedreact/renderer/components/<Name>Spec/*headers, which the macOS
Fabric component view imports.FocusZoneComponentViewdoes not provide a UI Automation peer; the generated CRTP
base returns none.ReactPackageProviderregisters nothing whenRNW_NEW_ARCHis undefined, so a
Paper Windows host gets no FocusZone.- The consuming-application integration exists but is stranded and divergent, not
absent. It targetspackages/agentic-components/storybook, whichmainmoved to
apps/storybookin
4c1855982
(PR storybook: move storybook into apps #4200),
and the file it changes most -windows-tests/storybook-smoke.test.cjs- has since
grown onmainto include a Button Overview keyboard case and ten focus-regression
cases. Inferred: this is a relocate-and-reconcile problem with a real merge
conflict in the smoke test, followed by revalidation, rather than integration work
that still has to be written. - Whether autolinking actually pulls
FRNFocusZone.vcxprojinto a solution generated
byinstall-windows-test-app --use-fabricis unproven; no build evidence was
available. Evidence gap, and the substantive part of what item 4 used to claim. - No new coverage in
apps/E2E/src/FocusZone; the
existing Windows and Win32 specs there still target the older path. The branch's new
focus assertions live in the Storybook harness, which no workflow invokes.
Observed. origin/user/jasonvmo/simple-focus-zone
(ac6d17e)
is an earlier, smaller version of the same refactor: primitive-only JavaScript, no
native Fabric work. Inferred: it is a subset of the Fabric branch and does not need
separate landing.
Scope
- Rebase or re-apply the Fabric branch onto current
main, accounting for the
Storybook repath and any framework-base API movement sinceffd510a. This includes
relocating the branch's Storybook integration - story glob, smoke-story entry,
agent-map.yaml, and agent orchestration script - from
packages/agentic-components/storybooktoapps/storybook. - Reconcile the branch's two FocusZone focus tests with the version of
windows-tests/storybook-smoke.test.cjsnow onmain, preserving the Button
Overview keyboard case and the ten focus-regression cases already there, and
revalidate the combined suite. - Resolve the
includesGeneratedCodeconflict between the Windows and macOS codegen
paths, and prove the resolution by building both. - Add a UI Automation peer and the correct control type for the Windows component
view, per the Windows Fabric reference. - Decide and implement the behavior when a Windows host is not on the New
Architecture: either a Paper registration or an explicit, documented no-op with a
diagnostic. - Prove the consuming-application linkage rather than assume it: verify that
install-windows-test-app --use-fabricpicks upFRNFocusZone.vcxprojthrough the
package'sreact-native.config.cjs, that the app builds and launches with it, and
that the macOS Fabric registration path resolves. - Preserve the Win32 Paper path: keep the
paperComponentName: 'RCTFocusZone'
mapping, keep the@office-iss/react-native-win32optional peer dependency, keep
the bridged prop names, and re-run the Win32 end-to-end suites unchanged. - Extend end-to-end coverage: directional, circular, tab-mode, disabled,
defaultTabbableElement, and 2D navigation on React Native Windows Fabric; raise the
macOS suite above its current two checks. The branch's two Storybook focus tests are
the starting point, not the finished set. - Decide how
packages/agentic/componentsconsumes roving focus. Open: adopt
FocusZone as a dependency, or add a JavaScript roving-tabindex hook in
framework-baseand keep FocusZone for the legacy catalog. Record the decision and
its consequences for wave 3 of the missing components.
Out of scope
- iOS and Android FocusZone implementations.
- Migrating the five legacy consumers off
foundation-composable. FocusTrapZone, except to note that it has the same shape and will need the same
treatment.- Implementing the wave 3 components themselves.
Deliverables
- A FocusZone package on
mainwith a working macOS Fabric component view and a
working React Native Windows Fabric native component. - A codegen configuration that produces correct output for both platforms, with the
includesGeneratedCodedecision documented in the package. - A UI Automation peer for the Windows component view.
- A stated, tested behavior for non-Fabric Windows hosts.
- Verified application integration: autolinking proven by a Windows build that
includes the project, plus the branch's FocusZone story, story glob, smoke-story
entry, and focus tests relocated onto the currentapps/storybooklayout and
merged with the focus-regression suite already there. - Windows Fabric end-to-end specs covering the documented prop matrix, and an
expanded macOS suite. - An unchanged, passing Win32 Paper end-to-end run.
- A written decision on how agentic components get roving focus, linked from the
wave 3 missing-component tasks. SPEC.mdin the package documenting per-platform support and the props each
platform honors.
Acceptance criteria
-
packages/components/FocusZonebuilds and its tests pass onmain. - A React Native Windows Fabric application hosting FocusZone builds, launches, and
performs arrow-key, Home/End, tab-mode, and 2D navigation as documented. - A macOS Fabric application hosting FocusZone builds, launches, and performs the
same navigation. - The Win32 Paper end-to-end suites in
apps/E2E/src/FocusZone
pass with no change to their expectations. - The Windows component view exposes a UI Automation peer with the correct control
type, verified from the automation tree. - Behavior on a non-Fabric Windows host is defined, implemented, and covered.
- The codegen configuration produces the headers both platforms need, proven by a
clean build of each. -
SPEC.mddocuments the supported platforms and per-platform prop support. - The roving-focus decision for
packages/agentic/componentsis recorded, and each
wave 3 missing-component task references it. - Windows Fabric end-to-end specs exist and run in the same harness as the existing
suites. - The branch's Storybook integration is relocated to
apps/storybookand its two
FocusZone focus tests coexist with the Button Overview keyboard case and the ten
focus-regression cases already onmain, with the combined suite passing. - Autolinking of
FRNFocusZone.vcxprojinto a solution generated by
install-windows-test-app --use-fabricis demonstrated, not assumed.
Dependencies and ordering
- Blocks the wave 3 missing components that need roving focus:
toolbar,tablist,
radio-group,nav,
simple-nav. - Blocks the focus-trap requirement in
drawer
anddialog. - Related to
menu,
list,dropdown,
andcombobox, whose keyboard models assume a
focus container. - Depends on the Infrastructure workstream's desktop test driver
(test-driver.md) for automated
cross-endpoint validation; manual validation is the fallback until it lands.
Risks and open decisions
- Two React Native forks in one codegen graph. The package must produce native
output forreact-native-macosandreact-native-windowsfrom one
codegenConfig.AGENTS.mdwarns that mixing platform forks
in a single type graph causes order-dependent confusion; the branch's
includesGeneratedCodeconflict is the concrete form of this risk. - First Windows native project in the repository. No package in the working tree
has awindows/Fabric project, so build, autolinking, and CI integration are all
unproven here. - Native versus JavaScript focus for agentic components. Adopting FocusZone gives
the agentic package a native dependency and a second React Native fork surface,
against the source boundary in
src/AGENTS.mdwhich limits
production source to React Native,design, andframework-base. A JavaScript
roving-tabindex hook stays inside that boundary but duplicates behavior the native
component already implements. Open decision. - Focus visuals. The agentic components render focus feedback through
FocusVisualbecause React Native Windows 0.81 can fail fast when border visuals
are created after mount
(src/components/AGENTS.md).
A native focus container must not reintroduce native focus visuals on that path. - Branch age. The Fabric branch predates the repath and has merge commits from
main; the rebase may be non-trivial. Open decision: rebase versus re-apply as
a fresh change. The Storybook integration is the sharpest case: the branch and
mainhave both changedwindows-tests/storybook-smoke.test.cjsin the same
region, so that file needs a deliberate merge rather than a take-one-side
resolution. - Win32 new-architecture status. Whether
@office-iss/react-native-win32supports
the Fabric component-name path at all was not determined; the plan relies on the
Paper name mapping continuing to resolve. Evidence gap.
Evidence and references
packages/components/FocusZone- working
tree state, includingmacos/,src/FocusZoneNativeComponent.ts, and
src/FocusZone.types.ts.apps/E2E/src/FocusZoneand
apps/tester-core/src/TestComponents/FocusZone- existing coverage that must keep passing.
apps/storybook/windows-tests/storybook-smoke.test.cjs,
apps/storybook/src/main.ts, and
apps/storybook/package.json- the current
location and content the branch's Storybook integration must be reconciled with;
@fluentui-react-native/focus-zoneis already a dependency here onmain.origin/user/jasonvmo/fabric-focus-zone@
ea2a704- the in-progress Fabric implementation.
origin/user/jasonvmo/simple-focus-zone@
ac6d17e- earlier primitive-only refactor.
.github/skills/agentic-component-authoring/references/windows-fabric-native-components.md- codegen, registration, and UI Automation requirements.
packages/agentic/components/specs/toolbar/blockers.md,
tablist/blockers.md,
radio-group/blockers.md,
menu/blockers.md-
the recorded focus blockers.
Sources retrieved 2026-08-20.
Activity
[-]Complete the Fabric FocusZone for macOS and Windows[/-][+]Components: Complete the Fabric FocusZone for macOS and Windows[/+]