Skip to content

Add Popover, Menu, MenuButton, and Dropdown for macOS, Windows, and Win32 - #4386

Merged
Jason Morse (JasonVMo) merged 10 commits into
mainfrom
user/jasonvmo/menu-popover
Oct 22, 2026
Merged

Jason Morse (JasonVMo) merged 10 commits into
mainfrom
user/jasonvmo/menu-popover

Conversation

@JasonVMo

@JasonVMo Jason Morse (JasonVMo) commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Platforms Impacted

  • iOS
  • macOS
  • win32 (Office)
  • windows
  • android

Description of changes

Adds the agentic Popover (#4236), Menu (#4232), MenuButton (#4231), and Dropdown (#4224) on macOS (Fabric and Paper), Windows Fabric, and Office Win32, built from owner-approved contracts. All four are exported from the @fluentui-react-native/components root along with their public types. Popover, Menu, MenuButton, Dropdown, MenuItem, ListboxItem, and Input are ratified as implemented: every requirement names realized evidence.

Summary

  • Popover (Components: Generate Popover #4236): a Button trigger slot and a token-styled surface in native-core's modern Callout, with
    controlled or uncontrolled presence, entry focus on the first eligible content or a fallback surface stop, and focus
    return on Escape or caller close.
  • Menu (Components: Generate Menu #4232) and MenuButton (Components: Generate MenuButton #4231): a shared popup family with two-level submenus declared through MenuItem's
    submenu prop, hover-open with platform delays, type-ahead, Home and End, discoverable disabled rows (R2), and native
    Tab continuation. MenuButton composes Button with a static chevron.
  • Dropdown (Components: Generate Dropdown #4224): a combobox trigger with Input's outline or underline chrome opens a list of ListboxItem
    options, with single or multiple selection, controlled or uncontrolled selection and presence, a placeholder, and a
    count summary. ListboxItem gains a value prop that binds it to the list.
  • Native-core: the modern Callout gained first and last entry focus, truthful dismissal reasons,
    tabNavigation with the tab-forward and tab-backward reasons, transparent content-drawn surfaces, nested-popup
    close and light-dismiss rules, and Paper command ordering through UI-manager blocks. Windows Fabric gained rounded
    popup windows with platform shadows, anchor-press handling, and a Tab message hook. New /win32 Callout and
    FocusZone adapters target the Office host's legacy registrations.
  • Shared popup controllers: Popover, Menu, and Dropdown read one platform popup host (common/popupHost) instead
    of five adapter files each, and share a presence latch (usePopupPresence). Menu and Dropdown also share the
    list-popup trigger controller (usePopupTrigger), the popup layout styles, and the ref and handler composition helpers.
    Dropdown reuses Input's visual-state resolution. Behavior is unchanged.
  • Framework Base and leaves: disabledFocusable for MenuItem, Input's variant focus treatment, and shared
    triggerExpandActions that declare Win32 Expand and Collapse.
  • Desktop driver: Windows window activation accepts owned popups, subtree walks retry on closing popups, and
    layout characters type as virtual keys.

Findings worth reviewing

  • Yoga's default errata let flexGrow rows fill a fit-content popup to the window width. Menu and Dropdown size their
    content with a row wrapper that uses overflow: 'scroll', and host nested popups in a wide absolute View.
  • Callout's popup-wide first/last search stops at a focusable FocusZone, so Menu entry focus uses FocusZone
    commands.
  • macOS Paper insets ScrollView content by the size of legacy scrollers (the style used while a mouse is attached),
    even while they are hidden. The Dropdown list therefore never requests a horizontal scroller and requests a vertical
    one only while its options overflow. Before this, Paper options were 17 points narrower than the list and the list
    was 17 points taller than its options. Fabric always uses overlay scrollers.
  • RNW's portal lays out its child as a layout root, so Dropdown's trigger-width floor is a zero-height child rather
    than minWidth.
  • The Callout placement regressions assumed room below the anchor. On the hosted runner's small display, the owner
    window sat low enough that native placement correctly flipped the popup above the anchor, which put it 267 points
    off. The regressions now move the owner window to the top of its screen first and restore it afterward.
  • Popover keeps its own trigger logic. It requests no pointer focus, holds focus per presentation, falls back to the
    surface, and does not restore focus after an assistive collapse. On Win32 it also keeps the raw Callout, because
    requestFocus('first') is unsupported when initialFocus is 'none'.
  • macOS driver: switchToWindow makes the owner window key, so story cases send keys before reading popup semantics,
    or act with the pointer inside the popup.

Deferred divergences and follow-ups

Item Components Tracking
Owner-relative Tab and Shift+Tab Popover, Menu, Dropdown #4377. Shift+Tab from a list or menu focuses the trigger instead of the view before it.
Edge margins Popover, Menu, Dropdown #4375. Native clamping aligns popups with the work-area edge without a margin.
Submenu intent on macOS Menu menu-submenu-intent: hover switching is delay-only; add a native pointer-travel signal.
Motion and the Popover arrow All Presentation is instant, which equals reduced motion. Revisit when Flex motion is final.
Context menus and point anchors Menu #4383 (D13).
Office Win32 host improvements All #4384 (R1, R3): honor gapSpace, per-pixel transparency, classified dismissals, FocusZone Home and End, trigger restoration on close, Tab past the owner, and one pointer enter without a leave. Each lets an adapter drop a workaround.
Dropdown tag strip Dropdown dropdown-token-strip: needs a reviewed Tag-based design.
RNmacOS renderer patch MenuItem, Input, Dropdown The root Yarn patch to RNmacOS 0.81.9 (Fabric AX roles and state, TextInput Tab stop, read-only editor) must go upstream or into each consuming host before release.
Office Apple runtime All test:apple-integration is a source probe; no Office runtime has run these components.
Controlled close veto Popover A caller that vetoes a trigger close keeps a stale close cause until the next open; recorded, not observed natively.
Win32 popup shadow All A one-pixel shadow artifact can appear at a bottom corner of the Win32 popup.

Known pre-existing failures

  • Windows lint-repo (Knip): knip.config.ts builds workspace keys with backslashes, so imported dependencies in
    unchanged packages read as unused. It passes on macOS.
  • Windows components-input--focus-treatment failed before this work.
  • The Windows node test controller occasionally exits with 0xC0000409 after the Dropdown Default story while the app
    stays responsive; reruns pass.

Verification

Shared popup controllers and CI fix (5f36f17), macOS, October 22, 2026

Check Result
Fabric smoke --macos --mode stories-and-tests Passed, zero skips: Dropdown 8/8, Menu 8/8, MenuButton 4/4, MenuItem 3/3, Popover 5/5, Input 4/4, native-modern-callout 16/16.
Paper prep --macos --paper, then smoke --macos --paper with the same selectors Passed, zero skips: the same counts.
CI Callout placement failure (y off by 267) Reproduced locally with the owner window low on the display; it passes after the fix on both renderers.
yarn workspace @fluentui-react-native/components test, lint, check:spec-contracts, report:spec-source-drift --offline --write Passed: 66 suites, 561 tests; 24 contracts; the drift report is unchanged.
yarn workspace @fluentui-react-native/native-core test and lint Passed: story typecheck, ownership, and 56 Jest tests.
yarn clean-lage-cache then yarn lage buildci Passed: 171 tasks, none skipped or failed; also yarn change:check and git diff --check.
PR CI at 5f36f17 Passed: all 18 checks, including macOS and macOS Paper Storybook, Windows and Win32 Storybook, and iOS.

Final commit (5cdbcad), macOS

Check Result
yarn workspace @fluentui-react-native/components test Passed: 66 suites, 561 tests, including the root-export test.
check:spec-contracts and report:spec-source-drift --offline --write Passed: 24 contracts validated.
yarn clean-lage-cache then yarn lage buildci Passed: 171 tasks, none failed or skipped.

Validation: macOS, October 21, 2026

macOS 27.0.1 (26A434), arm64, Xcode 26.4.1, Swift 6.3.1, RNmacOS 0.81.9 with the checked-in patch, Keyboard navigation
on (AppleKeyboardUIMode=3). Storybook commands use
yarn workspace @fluentui-react-native/agentic-components-storybook with STORYBOOK_SMOKE_TAG=story.

Check Result
Fabric storybook prep --macos, build --macos, smoke --macos --mode stories-and-tests Passed, zero skips: Dropdown 8/8, Menu 8/8, MenuButton 4/4, MenuItem 3/3, Popover 5/5, native-modern-callout 16/16, native-callout 2/2, native-modern-focuszone 4/4, components-*--*focus* 16/16; 199 stories rendered.
Paper prep --macos --paper, build --macos --paper, then the same selectors with --paper Passed, zero skips: the same counts. One native-modern-callout run hit a driver pointer-move error; the rerun passed 16/16.
Dropdown list measurement after the scroller fix, both renderers Observed: a 240×250 list with 230-point-wide, 40-point-tall options inside 4-point padding and a 1-point border.
Driver captures of the Dropdown trigger gallery, the open list, and the focus ring Observed: outline and underline, sizes, selected, error, and disabled triggers render correctly; the ring is correct once its animation settles.
yarn workspace @fluentui-react-native/native-core test:apple-integration Passed.
Components test, lint, check:spec-contracts Passed: 66 suites, 561 tests, 8 snapshots.
yarn clean-lage-cache then yarn lage buildci Passed: 171 tasks, zero skipped, pending, aborted, or failed; also storybook bundle --macos, app lint, yarn check-agent-instructions, yarn change:check, git diff --check.
ListboxItem story cases None defined; its stories render. LBI-006 runs through the Dropdown cases.

Validation: Windows Fabric and Office Win32, October 19 to 21, 2026

Check Result
Windows smoke --windows, Menu, MenuButton, MenuItem, Popover, Callout, and FocusZone Passed: Menu 8/8, MenuButton 4/4, MenuItem 3/3, Popover 5/5, native-callout 2/2, native-modern-callout 16/16, native-modern-focuszone 4/4.
Win32 smoke --win32, Menu family, Popover, and native-callout Passed: Menu 8/8, MenuButton 4/4, MenuItem 3/3, Popover 5/5, Callout 2/2.
desktop-focus cases on Menu, MenuButton, MenuItem, Popover, ListboxItem, and Dropdown Passed: 26/26 on Windows Fabric and 26/26 on Office Win32, before the macOS changes to the Dropdown cases.
Components, native-core, and desktop-driver lint and test; check:spec-contracts Passed: components 560 tests; native-core 18 ownership and 56 Jest tests; driver 61 with 3 skipped.
yarn clean-lage-cache then yarn lage buildci Knip failed, pre-existing on Windows (backslash workspace keys). Every other target passed; a deadline-sensitive storybook-desktop contract passed when rerun alone.

Windows Fabric and Office Win32 rerun at 8e32a1a, October 21, 2026

Check Result
smoke --windows and smoke --win32: Dropdown, Menu, MenuButton, MenuItem, Popover Passed on both, zero skips: Dropdown 8/8 (including the new Tab and Shift+Tab, trigger-close, and LongList cases), Menu 8/8, MenuButton 4/4, MenuItem 3/3, Popover 5/5.
Full indexed-story traversal Passed: 192 stories on Windows, 166 on Win32, including ListboxItem.
First Win32 attempt Node controller exited with 0xC0000409 after 14 passing cases; the full retry passed without source changes.

Not run

  • Interactive Windows Fabric and Office Win32 smokes of the shared popup controllers (5f36f17). The refactor
    replaces the Windows and Win32 adapters with common/popupHost.windows.ts and .win32.ts, and hosted CI disables
    keyboard and physical clicks on those lanes. Rerun the popup-family desktop-focus cases on the Windows machine.
  • VoiceOver, Narrator, and accessibility-action invocation on any endpoint.
  • Office runtime hosts on macOS; the high-contrast, scaling, and theme matrix.

The four components are exported before VoiceOver, Narrator, and visual-matrix qualification. The RNmacOS patch is still a release gate.

Before After
No Popover, Menu, MenuButton, or Dropdown in the agentic components package All four are implemented and exported on macOS, Windows, and Win32

Pull request checklist

This PR has considered (when applicable):

  • Automated Tests
  • Documentation and examples
  • Keyboard Accessibility
  • Voiceover
  • Internationalization and Right-to-left Layouts

Implement independent P2 prerequisites: disabled-focusable pressable/MenuItem targets with activation refusal and exactly-once release cleanup; Input outline/underline focus-visible treatment; modern Callout first/last focus strategies, observed dismissal causes, and duplicate/late-event fences. Keep legacy APIs stable and regenerate Windows bindings.

This is a blocked prerequisite handoff, not completion of Popover, Menu, MenuButton, or Dropdown. Those components remain unimplemented and unexported behind their P0/P1/native gates. Record the remaining work and exact checks in menu-popover-plan.md section 12. Reconcile reviewed R1-R3 and track Win32 host followups in #4384.

Owning tests pass (205 Framework Base, 484 components, 37 native-core plus 18 ownership/entrypoint checks); root TypeScript and Windows native builds pass. Native input-dependent cases are skipped, not qualified. Cache-cleared buildci is blocked by 11 Knip findings outside changed workspaces. Aggregate Storybook worker deadlines failed, then both affected contracts passed serial retry.

Next macOS Fabric and Paper: regenerate/build both renderers, run the named native cases and VoiceOver/visual checks, complete the React-module and Office bridging-header qualification, then finish family/continuation/semantic prerequisites and component contract review.
Next Windows Fabric: restore a physical-input-capable session, rerun Callout/shared-focus cases and UIA/Narrator, resolve the repository-check blockers, then continue native family/Tab/geometry/hover work before component delivery.
Next Office Win32: qualify host readiness/focus/family/semantics, implement the capability-gated adapter and approved Tab close-and-return interim, and track native host additions in #4384. Do not dispatch undeclared commands or claim root admission.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 67c62cd2-5b14-47f2-adf0-4317407f2d2c
Implement the agentic Dropdown (#4224) under its reviewed contract: a combobox
trigger that opens a list of ListboxItem options in single or multiple
selection mode, with entry focus on the selection, type-ahead, Home and End,
and selection accessibility actions. ListboxItem gains a value prop and an
option context binding (LBI-006). Windows Fabric floors the list at the
trigger width with an inner spacer because RNW portal layout replaces the
content root's min dimensions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ad2750d4-2e4b-4196-856f-aa0dba06a6ad
Dropdown passes its native story cases on macOS Fabric and Paper, along with
the Menu, MenuButton, MenuItem, Popover, Callout, FocusZone, and focus
regression selectors.

- Stop macOS Paper's legacy scrollers from insetting the Dropdown options:
  the list never requests a horizontal scroller and requests a vertical one
  only while its options overflow the viewport.
- Add Tab and Shift+Tab story cases (DRP-009) and make the list-semantics
  and LongList cases use pointer actions after switching to the popup window,
  which makes the owner window key on macOS.
- Record macOS qualification in the Dropdown SPEC and changeset.
- Reduce menu-popover-plan.md to the remaining gates, the decisions that
  still apply, and PR notes with per-platform validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… cap

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark Popover, Menu, MenuButton, Dropdown, MenuItem, ListboxItem, and Input
implemented now that every requirement names realized evidence, and export
the four new components and their public types from the package root.
Remove menu-popover-plan.md; its remaining gates and PR notes move to the
pull request.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Bundle size report

Tree-shaken, minified production esbuild bundles with React and React Native runtimes externalized.

Scenario Modules-Mac (Δ) Modules-Win (Δ) Size-Mac (Δ) Size-Win (Δ)
components-button 64 (+2) 65 (+2) 50.63k (+687b) 50.55k (+687b)
components-catalog 214 (+55) 215 (+55) 205.49k (+50.22k) 205.41k (+50.22k)
design-theme-state 34 (+1) 35 (+1) 30.15k (+42b) 30.08k (+42b)
design-themed-root 38 (+1) 39 (+1) 32.12k (+42b) 32.04k (+42b)
design-color-lib 6 (+0) 6 (+0) 6.33k (+0b) 6.33k (+0b)
design-theme-state-legacy-source 35 (+1) 36 (+1) 31.81k (+42b) 31.73k (+42b)
design-legacy-theme-from-flex 58 (+1) 59 (+1) 97.77k (+42b) 97.63k (+42b)

The job is advisory: size changes are reported but do not fail the pull request. Bundle or analysis errors still fail.


View bundle-size workflow run

Popover, Menu, and Dropdown now read one platform popup host
(common/popupHost) instead of five adapter files each, and share a
presence latch (usePopupPresence). Menu and Dropdown share the
list-popup trigger controller (usePopupTrigger), popup layout styles,
and ref and handler composition helpers. Dropdown reuses Input's
visual-state resolution. Behavior is unchanged.

The macOS Callout placement regressions failed in CI because the owner
window sat low on the hosted runner's small display, so native placement
correctly flipped the popup above the anchor. The regressions now move
the owner window to the top of its screen first and restore it after.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@JasonVMo
Jason Morse (JasonVMo) enabled auto-merge (squash) October 22, 2026 03:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants