[5377] UX: Ensure menus handle vertical overflow by n-lark · Pull Request #5448 · node-red/node-red · GitHub
Skip to content

[5377] UX: Ensure menus handle vertical overflow #5448

Merged
knolleary merged 4 commits intonode-red:devfrom
n-lark:5377/ux-menu-vertical-overflow
Mar 10, 2026
Merged

[5377] UX: Ensure menus handle vertical overflow #5448
knolleary merged 4 commits intonode-red:devfrom
n-lark:5377/ux-menu-vertical-overflow

Conversation

@n-lark
Copy link
Copy Markdown
Contributor

@n-lark n-lark commented Jan 22, 2026

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Proposed changes

Resolves #5377

🧪 See my test plan and proposed changes here.
📜 See my written explanation here.

Checklist

  • I have read the contribution guidelines
  • For non-bugfix PRs, I have discussed this change on the forum/slack team.
  • I have run npm run test to verify the unit tests pass

Note on CI test failure:

The 1 failing test watch Node - should watch a file to be changed is unrelated to my changes - it also fails on master.
My changes only touch menu/UI files (menu.js, tabs.js, contextMenu.js, SCSS), none of which interact with the file watcher node that I am aware of. Happy to log a separate issue for this test if helpful!

  • I have added suitable unit tests to cover the new/changed functionality

@n-lark
Copy link
Copy Markdown
Contributor Author

n-lark commented Jan 22, 2026

@n-lark
Copy link
Copy Markdown
Contributor Author

n-lark commented Jan 22, 2026

Node-RED Contribution: Written Explanation 📝

🧪 See my test plan and proposed changes here.

1. What did you choose to build, and why?

Problem Selection 🎯

I chose to address Issue #5377: "UX: Ensure menus handle vertical overflow" from the Node-RED repository.

The original issue: "Our current menus (main menu, context menu) do not cope well if there is not enough vertical space to show them. If the bottom is off-screen, the options need to be scrollable so they can be accessed."

Why this problem:

  • Clear scope: The issue had a well-defined problem statement and reproducible behavior
  • Real user impact: This affects usability when working with Node-RED on smaller screens or with many menu items
  • Reasonable timebox: I estimated this could be understood and implemented within 2-3 hours
  • Technical learning: It involved both understanding an existing codebase and solving a fundamental CSS limitation

My Interpretation 🧩

The core challenge was: How do you enable vertical scrolling on a menu while allowing submenus to escape horizontally?

The submenu problem I discovered: While testing the initial vertical scrolling implementation, I noticed a critical UX issue: when adding overflow-y: auto to make menus scrollable, submenus got clipped inside the scrolling container. The natural CSS approach (overflow-y: auto + overflow-x: visible) doesn't work due to CSS specification constraints - when one axis is set to auto/scroll, the other automatically becomes auto as well, clipping any horizontally-extending submenus.

This wasn't explicitly mentioned in the original issue, but I recognized it would create a poor user experience. Users wouldn't be able to access submenu items if the parent menu needed to scroll. I decided to scope this into the fix to ensure a complete, polished solution.

What I Focused On ⚡

Core functionality:

  1. Detection logic - Identify when a submenu needs special positioning (parent is scrolling OR submenu would overflow viewport)
  2. Portal pattern - Move submenus to <body> to escape overflow containers
  3. Manual positioning - Calculate positions using getBoundingClientRect() with flip logic inspired by Floating UI
  4. Theme preservation - Maintain dark/light theme styling when submenus are portaled

Implementation locations:

  • Header menus (hamburger, deploy, user menus) - Dark theme
  • Context menus (workspace right-click) - Light theme
  • Tab menus (flow dropdowns) - Light theme

What I Intentionally Left Out 🔪

Scope decisions:

  1. Window resize handling - Menus don't reposition when window resizes while open (documented as known limitation)
  2. Double-nested submenus - Only single-level submenu portaling (Node-RED doesn't currently have nested submenus)
  3. External dependencies - Didn't add Floating UI library despite it solving this elegantly, to respect Node-RED's minimal dependency philosophy
  4. Automated tests - Focused on manual testing given the visual/interactive nature and time constraints

Implementation Tradeoffs ⚖️

Architectural choices:

  • Manual positioning over library: Replicated Floating UI's core positioning logic instead of adding the dependency. This increases maintenance burden slightly but keeps bundle size minimal.
  • Class-based theming over inheritance: When portaling to <body>, CSS inheritance breaks. I created standalone theme classes (.red-ui-header-menu, .red-ui-menu-dropdown-portaled) rather than trying to preserve the original DOM structure.
  • IIFE closures over global state: Each submenu item gets its own closure to track state, avoiding global pollution at the cost of slightly more memory per item.

Accepted technical debt:

  • Duplicated styling: Created SASS mixins to encapsulate menu theming and scrollbar styles, but this means menu styling now exists in two places. A future improvement would consolidate all menu styling to use these mixins.
  • Repeated scroll logic: The scroll-enabling pattern (check if menu exceeds viewport, apply maxHeight and overflowY: auto) is duplicated across menu.js and tabs.js in three places. A future refactor could extract this into a shared helper function like applyScrollingIfNeeded(menu, top).
  • Positioning edge cases: The getSubmenuPosition() function uses hardcoded fallback dimensions (offsetWidth || 230, offsetHeight || 200) and viewport-based calculations that may behave unexpectedly with non-100% zoom, RTL layouts, or if menus were ever rendered inside scroll containers (currently they're appended to <body>, so this works).

These are acceptable tradeoffs for the timebox, but would be good candidates for follow-up improvements if the implementation is adopted.


2. How and why did I use AI? 🦾

I used Claude (Anthropic's AI assistant) extensively throughout this work. Here's how:

The Brainstorming Phase: What Didn't Work 🪦

I explored several CSS-only approaches (wrapper elements, overflow tricks, contain property, transform-based positioning), including patterns from CSS-Tricks and AI research. All failed due to the same underlying CSS constraint or broke Node-RED's existing menu behavior. This made it clear the solution needed to escape the overflow container entirely rather than fight CSS limitations. 💀

The Final Solution: Taking Modern Libraries as Inspiration 💡

After these dead ends, I realized this is a solved problem in the positioning library space. Libraries like Floating UI (the modern successor to Popper.js) exist specifically to handle floating element positioning - tooltips, dropdowns, and popovers that need to escape overflow containers with intelligent collision detection and flip behavior.

This sparked the key insight: rather than reinventing the wheel, study how battle-tested libraries solve this problem, then adapt those patterns to Node-RED's vanilla JS architecture. 🔍

AI helped me research Floating UI's approach and understand its core algorithms, but the strategic decision was mine: instead of adding the dependency, replicate its core positioning patterns in vanilla JS matching Node-RED's existing style.

Why not just use Floating UI directly? Floating UI would have solved this elegantly, but Node-RED intentionally avoids frontend dependencies (the editor package.json has zero npm dependencies). Adding it would introduce new maintenance overhead and set a precedent inconsistent with the existing editor architecture. Instead, replicating Floating UI's well-documented positioning patterns in vanilla JS respected the project's philosophy while delivering the same functionality.

What AI Helped With & What I Did Myself 🤖 🧠

AI handled (80% of code generation):

  • Navigating the Node-RED file structure and understanding existing patterns
  • Researching Floating UI's positioning patterns after I suggested it
  • Translating Floating UI patterns into vanilla JS
  • Writing the positioning logic (getSubmenuPosition(), doesSubmenuNeedPortaling())
  • Creating SASS mixins for DRY styling (I suggested this approach so future menu implementations could reuse the theming and scrollbar patterns)
  • Code review and style consistency
  • Formatting and organizing the testing plan and implementation documentation

I handled (100% of decision-making):

  • Choosing which issue to tackle from the Node-RED backlog
  • Key insight: Suggesting we look at Floating UI's approach after CSS solutions failed
  • Deciding against adding Floating UI as a dependency
  • Defining the trigger conditions: portal submenus when parent menu is scrolling OR when submenu would overflow viewport edges
  • Choosing the IIFE closure pattern for state management to match existing code
  • Recognizing when AI suggestions were too framework-oriented (e.g., React patterns vs. jQuery/IIFE)
  • Manual testing across all menu types and browsers
  • UX validation - does it "feel right"?
  • Scoping what's in/out (no window resize handling, no nested submenus)

Tradeoffs in AI usage: 🎭

I chose to use AI heavily for code generation because:

  • Time constraints: 2-3 hour timebox meant speed was critical
  • Accelerated exploration: AI could rapidly navigate Node-RED's structure to find menu-related code and existing patterns
  • Translation work: Converting Floating UI's patterns to vanilla JS was repetitive but not architecturally complex

I retained control over decisions because:

  • Architectural judgment: AI can't assess project philosophy or make dependency decisions
  • Pattern recognition: AI initially suggested React patterns; recognizing the vanilla JS/IIFE style required human judgment
  • UX validation: Only manual testing could confirm the implementation "felt right"

This balance let me deliver a working solution within the timebox while ensuring it fit Node-RED's architecture and standards.


Summary 🎬

This contribution was intentionally scoped to fit a 2–3 hour timebox while still delivering a complete, user-safe solution. I focused on solving the underlying UX problem rather than applying a partial fix, even when that meant identifying edge cases not explicitly called out in the original issue.

I made deliberate tradeoffs to align with Node-RED's existing architecture and philosophy, favoring minimal dependencies, small surface area changes, and predictable behavior.

AI was used as an accelerator for exploration and implementation, but all architectural decisions, scoping choices, and UX validation were made deliberately by me to ensure the solution fit Node-RED's standards and user needs.

@n-lark n-lark marked this pull request as ready for review January 22, 2026 23:17
@knolleary
Copy link
Copy Markdown
Member

Thanks for the contribution and write up @n-lark

I'll review the details and feedback.

@n-lark n-lark force-pushed the 5377/ux-menu-vertical-overflow branch from 3c3a2f1 to b6cb9e0 Compare March 6, 2026 20:08
@n-lark n-lark changed the base branch from master to dev March 6, 2026 20:09
@n-lark
Copy link
Copy Markdown
Contributor Author

n-lark commented Mar 6, 2026

@n-lark n-lark changed the title UX: Ensure menus handle vertical overflow [5377] UX: Ensure menus handle vertical overflow Mar 6, 2026
Comment thread packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js Outdated
Copy link
Copy Markdown
Member

@knolleary knolleary left a comment

Choose a reason for hiding this comment

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

A couple of suggested changes to fix up a couple behaviours that didn't feel right to me.

  1. Context menu: move it up if possible to avoid scrolling. If scrolling unavoidable, use the full height available.
  2. First tier of Submenus: make them scrollable if necessary.

Copy link
Copy Markdown
Member

@knolleary knolleary left a comment

Choose a reason for hiding this comment

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

Have applied a couple changes - looking good now

@knolleary knolleary merged commit b163f38 into node-red:dev Mar 10, 2026
5 checks passed
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.

UX: Ensure menus handle vertical overflow

2 participants