Skip to content

feat: Complete FAB Menu behavior and web adaptation - #5170

Open
alpharius-ck wants to merge 6 commits into
mainfrom
refactor-fab
Open

alpharius-ck wants to merge 6 commits into
mainfrom
refactor-fab

Conversation

@alpharius-ck

@alpharius-ck alpharius-ck commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

FAB.Menu did not follow the Material 3 spec or the other v6 components in a few ways. On web it did not tell assistive technology whether it was open. Collapsed items stayed in the tab order and the accessibility tree. Items had no hover, focus or press feedback, and a menu taller than the screen could not scroll.

This PR fixes those. Dismissal (Escape, Android back, outside press, screen reader escape) and focus handling (focus on open, restore on close) are intentionally left out and belong to the overlay work in #5127. A follow-up will wire FAB.Menu into that portal stack. Until then, the menu closes only through its trigger and items, as on main.

Builds on #5110 (variant rename). It also covers the aria-expanded change from #5151, so one of the two should be dropped when they land.

Related issue

https://app.notion.com/p/callstack/Complete-FAB-Menu-behavior-and-web-adaptation-3ac5d027c0f8806ba4f8f5a8cc061eac?v=3285d027c0f880a68ccb000c1ce3c56e&source=copy_link

Screenshots / Videos

fab-ios fab-general fab-android fab-web

Test plan

Use the example app: FAB → menu (yarn example web, yarn example ios, yarn example android).

Web

  1. In dev tools, the closed trigger has aria-expanded="false" and aria-haspopup="menu". After opening, it has aria-expanded="true", and the items container has role="menu".
  2. With the menu closed, Tab does not stop on any item. With it open, Tab reaches the items.
  3. With focus on an item, ArrowUp/ArrowDown, Home and End move focus through the items and then the close button, and wrap around. Arrow keys do nothing when focus is outside the menu.
  4. Hover, keyboard-focus and press an item. The state layer and elevation change. The focus ring appears for keyboard focus only, not mouse clicks. The state resets after the menu closes.
  5. Shrink the window height so the items don't fit. They scroll, the close button stays reachable, and the scroll position returns to the top each time the menu opens.

iOS / Android

  1. On a short screen or in landscape (Android: adb shell wm size 1080x1000), the items scroll to the last one. Reopening starts at the top.
  2. With VoiceOver or TalkBack on, collapsed items are not announced. Expanded items are announced, and the trigger is announced as expanded or collapsed.
  3. Press and hold an item (touch), or hover it on iPad with a pointer. The state layer shows and clears.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Found potential problems with the pull request:

  • The description is too long. Please keep it under 1000 characters.

alpharius-ck and others added 4 commits November 5, 2026 17:01
# Conflicts:
#	src/components/FAB/Shell.tsx
The previous merge of main kept the old versions of most files that
changed on main. Bring the tree to main plus the FAB menu changes so the
PR only shows the FAB files.

Type the FAB refs with ComponentRef<typeof View> and
ComponentRef<typeof ScrollView>, as main does, since View and ScrollView
are no longer instance types on React Native 0.88.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Doberjohn

Copy link
Copy Markdown
Collaborator

Thanks for the videos @likevy, they look good. After I updated the branch, Typecheck and Build package fail: React Native 0.88 (from #5167) now types onKeyDown and onKeyUp on View and Pressable with its own key event, so the React DOM KeyboardEvent in useSpaceKeyPress no longer fits. Replacing it with only what the handlers read fixes it and passes the legacy types check too:

type KeyboardEvent = {
  nativeEvent: { key: string; repeat?: boolean | undefined };
  preventDefault: () => void;
};

Could you push that? Then I'll do the last checks and merge.

@alpharius-ck

Copy link
Copy Markdown
Collaborator Author

Thanks for the videos @likevy, they look good. After I updated the branch, Typecheck and Build package fail: React Native 0.88 (from #5167) now types onKeyDown and onKeyUp on View and Pressable with its own key event, so the React DOM KeyboardEvent in useSpaceKeyPress no longer fits. Replacing it with only what the handlers read fixes it and passes the legacy types check too:

type KeyboardEvent = {
  nativeEvent: { key: string; repeat?: boolean | undefined };
  preventDefault: () => void;
};

Could you push that? Then I'll do the last checks and merge.

sure - would do asap

@alpharius-ck

Copy link
Copy Markdown
Collaborator Author

@Doberjohn branch is working correctly with RN 0.88

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.

2 participants