Skip to content

feat(menu): modernize Menu to Material Design 3 specs - #5073

Open
mikolajadamowicz wants to merge 13 commits into
callstack:mainfrom
mikolajadamowicz:menu-md3
Open

feat(menu): modernize Menu to Material Design 3 specs#5073
mikolajadamowicz wants to merge 13 commits into
callstack:mainfrom
mikolajadamowicz:menu-md3

Conversation

@mikolajadamowicz

Copy link
Copy Markdown

PR description

Takes over #5037 (original author @matkoson). All commits keep their original
authorship; this branch rebases them onto current main and addresses the
review feedback from @MikitasK. Closing #5037 in favour of this one.

Motivation

Modernize Menu / Menu.Item to the latest Material Design 3 specs. Reuse theme tokens (shape, typography, motion, color roles) and extract component tokens in the same pattern as Checkbox / FAB / ConnectedButtonGroup.

Spec re-check (M3 menus)

Re-checked M3 menus specs and overview:

  • Container shape corner.large; item first/last/selected corner.medium.
  • Label labelLarge; supporting bodySmall; trailing supporting labelLarge, aligned to the trailing edge.
  • Default container fill is MD3 surfaceContainerLow. The elevation prop controls shadow only (Paper's elevation.level2 maps to different surfaceContainer tones, so it is not used as the menu fill).
  • Selected items use tertiaryContainer / onTertiaryContainer.
  • Expressive choice: optional colorScheme="vibrant" on Menu and Menu.Item.

Changes

Tokens / Menu / Menu.Item

  • New Menu/tokens.ts with sizes, shapes, typography and color roles for both schemes
  • Container fill surfaceContainerLow; surface corner.large; first/last/selected item corner.medium applied through a layout context (composeMenuChildren + context.tsx) — no cloneElement, no displayName filtering
  • Menu.Item gains supportingText, trailingSupportingText, selected, colorScheme, roundedTop / roundedBottom, matching supportingTextStyle / trailingSupportingTextStyle, and supportingTextMaxFontSizeMultiplier / trailingSupportingTextMaxFontSizeMultiplier
  • Title moves to labelLarge; selected items use tertiary roles, with disabled winning over selected via content opacity
  • New Menu/motion.ts: spring open/close driven by toRawSpring, snapping instead when reduce-motion is on
  • colorScheme on Menu: standard | vibrant

Example / docs / tests

  • Example: selection, supporting text, trailing shortcuts and the vibrant scheme
  • Docs and theme color tables updated for the selected / vibrant roles; PropTables regenerated; refreshed Android + iOS screenshots
  • Unit tests for shape, colors, supporting text, dense sizing, and the motion / reduce-motion paths

Test infrastructure

  • jest/setNodeEnv.js sets NODE_ENV=test before React Native loads, so Animated force-updates host styles and toHaveStyle can see them
  • jest/testSetup.js stubs Animated.spring the same way Animated.timing was already stubbed, so native-driver animations don't fail under Jest

Review feedback from #5037

All five inline comments from @MikitasK are addressed:

  • Menu.Item now falls back to MenuRootContext for colorScheme, so items rendered inside a wrapper or Fragment — which never receive MenuItemLayoutContext — no longer drop back to standard inside a vibrant menu
  • supportingText and trailingSupportingText use explicit presence flags instead of truthiness, so numeric values such as 0 render while boolean placeholders stay ignored
  • Rows with only trailingSupportingText grow with their content instead of keeping a fixed 48/32dp height, which previously clipped at large accessibility font sizes

Each fix has a regression test that fails without it.

Two further accessibility / spec fixes

Found while verifying the above on device:

  • Supporting text ignored the font-scale cap. The title passed maxFontSizeMultiplier, the two supporting texts passed nothing, so at accessibility sizes they outgrew the title they belong to and truncated. Added supportingTextMaxFontSizeMultiplier and trailingSupportingTextMaxFontSizeMultiplier, both defaulting to 1.5 to match the title. Naming follows the existing ListItem pair.
  • Trailing supporting text was not on the trailing edge. The row shrink-wrapped its content, so ⌘C sat next to the label rather than at the menu edge where M3 puts it. The row now stretches and the trailing text uses an auto left margin. Menu width is unchanged.

Related issue

Fixes #4977

Test plan

  • yarn typecheck / yarn lint / full unit test suite green (55 suites, 762 tests, 169 snapshots)
  • docs tsc --noEmit
  • iOS simulator (iPhone 17, iOS 26.5): standard menu, selected + supporting text, trailing shortcut, vibrant scheme, and an accessibility-XXXL font pass
  • Android visual matrix (Pixel 9 Pro XL emulator)
  • iOS visual matrix (iPhone 16 Pro simulator)

Visual verification

Platform closed icons + disabled selected + supporting vibrant bottom anchor
Android (Pixel 9 Pro XL) a-closed a-icons a-sel a-vib a-bot
iOS (iPhone 16 Pro) i-closed i-icons i-sel i-vib i-bot

@MikitasK MikitasK left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

thanks for addressing all my #5037 comments, nothing else from my side 👌
just make sure to cover all @JKobrynski comments before merging the PR

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.

feat: modernize Menu to the latest Material Design specs

3 participants