Skip to content

CustomSelectControlV2: Flip legacy popover upward when space below trigger is insufficient#77947

Closed
BugReportOnWeb wants to merge 3 commits intoWordPress:trunkfrom
BugReportOnWeb:fix/custom-select-legacy-popover-flip
Closed

CustomSelectControlV2: Flip legacy popover upward when space below trigger is insufficient#77947
BugReportOnWeb wants to merge 3 commits intoWordPress:trunkfrom
BugReportOnWeb:fix/custom-select-legacy-popover-flip

Conversation

@BugReportOnWeb
Copy link
Copy Markdown
Contributor

What?

Closes #76126

Fixes an issue in the CustomSelectControlV2 legacy adapter where the popover was forced to never flip. This caused dropdowns to always open downward and get clipped by the viewport when used near the bottom of the Style Panel.

This change introduces a viewport-aware flip behavior only for the legacy adapter. The popover flips upward when there isn’t enough space below for proper interaction, and applies additional padding to prevent it from rendering beneath the sticky header. The non-legacy component remains unchanged.

Why?

PR #63357 intentionally disabled flipping for the legacy adapter to align with downshift-based component. However, this introduced a regression (#76126) where dropdowns near the bottom of the Style Panel could become partially or fully inaccessible due to viewport clipping.

The fix preserves the original no-flip behavior in the common case (enough space below the trigger) so the block toolbar glitch is not reintroduced, while allowing a flip only when the space below is genuinely insufficient and ensuring it remains unobstructed by the panel’s header.

How?

On onPointerDown of the trigger button:

  • The available space below the trigger is measured
  • If the space is less than FLIP_THRESHOLD (currently set to 150px), flip is enabled for that interaction
  • This causes the popover to render upward for that open cycle only

Additionally, when the popover flips upward, overflowPadding={50} is applied to offset it from the top edge of the viewport, preventing it from appearing beneath the block toolbar’s sticky header.

Testing Instructions

  1. Open the Site Editor or Block Editor with the Style Panel visible
  2. Select a block with a legacy combobox (e.g., Font Size or Border Radius)
  3. Scroll to the bottom of the Style Panel so the trigger is near the viewport edge
  4. Click the combobox - dropdown should open upward, fully visible, and not obscured by the header.
  5. Scroll back up where there is enough space below
  6. Click again - dropdown should open downward as usual

Screenshots or screencast

Screen.Recording.2026-05-05.at.4.01.44.PM.mov

Use of AI Tools

  • ChatGPT for refining the PR description
  • Claude Sonnet for debugging a race condition

@github-actions github-actions Bot added the [Package] Components /packages/components label May 5, 2026
@BugReportOnWeb BugReportOnWeb marked this pull request as ready for review May 5, 2026 12:13
@BugReportOnWeb BugReportOnWeb requested review from a team and ajitbohra as code owners May 5, 2026 12:13
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: [Package] Components.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @jayclydeTags.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: jayclydeTags.

Co-authored-by: BugReportOnWeb <devasheeshkaul@git.wordpress.org>
Co-authored-by: Mustafabharmal <mustafabharmal@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@ciampo
Copy link
Copy Markdown
Contributor

ciampo commented May 5, 2026

Thanks for digging into this!

After looking at this with the linked context (#63357, #63180, #62821) and tracing the actual rendering, I'd suggest closing this PR in favor of a fix at a different layer. Details below.

Concerns with the current implementation

  • Keyboard activation is not covered. The flip is decided in onPointerDown, which doesn't fire on Enter/Space. The legacy adapter is keyboard-opened (showOnKeyDown={ ! isLegacy }), so keyboard users near the bottom of the Style Panel still hit the original bug. This is the bug we're trying to fix, so missing the keyboard path makes the fix incomplete.
  • It re-implements (a worse version of) what Ariakit/Floating UI already do. spaceBelow is computed once on pointerdown and then frozen for the open cycle — no reaction to scroll, resize, or content height changes. Floating UI does all of this dynamically when flip is enabled.
  • FLIP_THRESHOLD = 150 is a magic number not derived from anything observable. A 100px popover that fits perfectly will still flip; a 600px popover that won't fit even after flipping will flip anyway. The right value is "the popover's actual rendered height", which Floating UI already considers.
  • overflowPadding = 50 bakes a block-editor concern into a generic component. The 50px is approximating the block toolbar's sticky header (CustomSelectControl V2 popover renders below position: sticky elements #63180), but CustomSelectControlV2 lives in @wordpress/components — a generic, WP-agnostic UI library that's also published to npm and used outside the block editor. The component shouldn't reserve space for one specific consumer's layout.
  • onPointerDown silently overrides any consumer-supplied handler because it's set after { ...restProps } on <CustomSelectButton>. Easy to regress later.
  • The race condition mentioned in the PR description is itself a smell. The fact that flip semantics depend on the order of pointerdown -> setState -> Ariakit open suggests we're working at the wrong layer. If we let Floating UI position the popover from the start, the ordering question goes away.
  • The link in the source comment is broken (https://github.com/WordPress/gutenberg/issues/ — missing #76126).

What's actually going on

Reading Ariakit's source, Popover defaults to modal=false, and portal = !!modal. So Ariakit.SelectPopover (which our Styled.SelectPopover extends) does not portal by default — it renders inline, in the DOM tree where it was declared. The Popover docs don't really call this out.

That single fact explains both issues:

Both issues collapse into a single root cause: the popover renders inline, in stacking contexts and overflow containers it shouldn't be subject to.

Suggested path

We should look into potentially enabling the underlying Styled.SelectPopover to portal instead of rendering inline when not modal. We should undersand the best course of action:

cc @mirka since this is partially related to the work we're actively doing re. reducing/simplifying z-stacking in Gutenberg.

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

Labels

[Package] Components /packages/components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Component: enhance Listbox dialog/combobox screen adaptability

2 participants