Skip to content

Components: Complete the Fabric FocusZone for macOS and Windows #4217

Description

@JasonVMo

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:

  1. codegenConfig.includesGeneratedCode is true on 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 generated react/renderer/components/<Name>Spec/* headers, which the macOS
    Fabric component view imports.
  2. FocusZoneComponentView does not provide a UI Automation peer; the generated CRTP
    base returns none.
  3. ReactPackageProvider registers nothing when RNW_NEW_ARCH is undefined, so a
    Paper Windows host gets no FocusZone.
  4. The consuming-application integration exists but is stranded and divergent, not
    absent. It targets packages/agentic-components/storybook, which main moved to
    apps/storybook in
    4c1855982
    (PR storybook: move storybook into apps #4200),
    and the file it changes most - windows-tests/storybook-smoke.test.cjs - has since
    grown on main to 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.
  5. Whether autolinking actually pulls FRNFocusZone.vcxproj into a solution generated
    by install-windows-test-app --use-fabric is unproven; no build evidence was
    available. Evidence gap, and the substantive part of what item 4 used to claim.
  6. 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

  1. Rebase or re-apply the Fabric branch onto current main, accounting for the
    Storybook repath and any framework-base API movement since ffd510a. This includes
    relocating the branch's Storybook integration - story glob, smoke-story entry,
    agent-map.yaml, and agent orchestration script - from
    packages/agentic-components/storybook to apps/storybook.
  2. Reconcile the branch's two FocusZone focus tests with the version of
    windows-tests/storybook-smoke.test.cjs now on main, preserving the Button
    Overview keyboard case and the ten focus-regression cases already there, and
    revalidate the combined suite.
  3. Resolve the includesGeneratedCode conflict between the Windows and macOS codegen
    paths, and prove the resolution by building both.
  4. Add a UI Automation peer and the correct control type for the Windows component
    view, per the Windows Fabric reference.
  5. 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.
  6. Prove the consuming-application linkage rather than assume it: verify that
    install-windows-test-app --use-fabric picks up FRNFocusZone.vcxproj through the
    package's react-native.config.cjs, that the app builds and launches with it, and
    that the macOS Fabric registration path resolves.
  7. Preserve the Win32 Paper path: keep the paperComponentName: 'RCTFocusZone'
    mapping, keep the @office-iss/react-native-win32 optional peer dependency, keep
    the bridged prop names, and re-run the Win32 end-to-end suites unchanged.
  8. 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.
  9. Decide how packages/agentic/components consumes roving focus. Open: adopt
    FocusZone as a dependency, or add a JavaScript roving-tabindex hook in
    framework-base and 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

  1. A FocusZone package on main with a working macOS Fabric component view and a
    working React Native Windows Fabric native component.
  2. A codegen configuration that produces correct output for both platforms, with the
    includesGeneratedCode decision documented in the package.
  3. A UI Automation peer for the Windows component view.
  4. A stated, tested behavior for non-Fabric Windows hosts.
  5. 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 current apps/storybook layout and
    merged with the focus-regression suite already there.
  6. Windows Fabric end-to-end specs covering the documented prop matrix, and an
    expanded macOS suite.
  7. An unchanged, passing Win32 Paper end-to-end run.
  8. A written decision on how agentic components get roving focus, linked from the
    wave 3 missing-component tasks.
  9. SPEC.md in the package documenting per-platform support and the props each
    platform honors.

Acceptance criteria

  • packages/components/FocusZone builds and its tests pass on main.
  • 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.md documents the supported platforms and per-platform prop support.
  • The roving-focus decision for packages/agentic/components is 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/storybook and its two
    FocusZone focus tests coexist with the Button Overview keyboard case and the ten
    focus-regression cases already on main, with the combined suite passing.
  • Autolinking of FRNFocusZone.vcxproj into a solution generated by
    install-windows-test-app --use-fabric is demonstrated, not assumed.

Dependencies and ordering

Risks and open decisions

  1. Two React Native forks in one codegen graph. The package must produce native
    output for react-native-macos and react-native-windows from one
    codegenConfig. AGENTS.md warns that mixing platform forks
    in a single type graph causes order-dependent confusion; the branch's
    includesGeneratedCode conflict is the concrete form of this risk.
  2. First Windows native project in the repository. No package in the working tree
    has a windows/ Fabric project, so build, autolinking, and CI integration are all
    unproven here.
  3. 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.md which limits
    production source to React Native, design, and framework-base. A JavaScript
    roving-tabindex hook stays inside that boundary but duplicates behavior the native
    component already implements. Open decision.
  4. Focus visuals. The agentic components render focus feedback through
    FocusVisual because 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.
  5. 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
    main have both changed windows-tests/storybook-smoke.test.cjs in the same
    region, so that file needs a deliberate merge rather than a take-one-side
    resolution.
  6. Win32 new-architecture status. Whether @office-iss/react-native-win32 supports
    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

Sources retrieved 2026-08-20.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions