Skip to content

[Epic] Reduce or eliminate unnecessary portalling in EUI componentsย #9503

@JasonStoltz

Description

@JasonStoltz

Summary / Pitch

Target EUI release date(s): TBD

Portalling in EUIโ€”rendering overlay components like flyouts, modals, and popovers into document.bodyโ€”is causing UI crashes, React compatibility issues, and conflicts with Kibana's new layout architecture. This initiative reduces or eliminates unnecessary portalling to improve stability and unblock key Kibana features.

Context

Portalling was originally added to solve z-index and overflow issues, but it has become a source of significant problems:

  1. UI Crashes โ€” Push flyouts throw NotFoundError during concurrent DOM operations when portal elements are removed mid-render ([EuiFlyout] type="push" throws NotFoundError on unmount during concurrent DOM operationsย #9389). This crashes the UI with no recovery path.

  2. React Strict Mode Incompatibility โ€” Portal divs are created twice and orphaned divs aren't cleaned up, causing DOM bloat that accumulates with user interactions ([Strict Mode] Redundant div is portalled and not cleaned upย #8784). This affects every EUI consumer using React 18's Strict Mode.

  3. Kibana Layout Blocked โ€” The new Kibana Workspace Chrome grid layout requires flyouts and popovers to be scoped to application containers, not document.body. Current portalling behavior prevents this ([Meta][Kibana Workspace Chrome] Improvements for new Kibana layoutย #8820).

  4. z-index Wars โ€” Portalled elements frequently appear above or below other UI elements unexpectedly, requiring constant workarounds ([EuiPopover] Popover is placed above header (z-index)ย #4756, [EuiComboBox] Combobox popover can be hidden behind EuiBottomBarย #4872, EuiGlobalToastList became hidden behind EuiFlyout with EUI 7.14.1ย #5169, [EuiFlyout] [EuiButtonGroup] Z-Index conflict when a Flyout occludes Toggle Buttonsย #4122).

  5. SSR Incompatibility โ€” Portal components fail during server-side rendering because document is not defined ([EuiBottomBar] document is not defined when using server renderingย #5656).

This is not a new concern. Issue #9255 explicitly states: "Long term, consider what could be done about portaling generally in EUI, which is overused, unnecessary, and problematic."

Goals

  • Eliminate unnecessary portalling where CSS-only solutions (e.g., position: fixed) suffice
  • Provide container-scoped alternatives for components that currently portal to document.body
  • Resolve React 18 Strict Mode compatibility issues
  • Unblock Kibana Workspace Chrome layout requirements
  • Reduce portal-related bug reports (currently 71+ issues filed)

Out of scope

  • Complete removal of all portalling (some edge cases may still require it)
  • Changing the public API signatures of affected components (deprecation path should be gradual)
  • Fixing third-party portal libraries (e.g., react-reverse-portal)

Scope of Work

12 components currently rely on portalling:

Component Portal Method Proposed Change
EuiPopover EuiPortal (always) Evaluate if portalling is necessary; add usePortal opt-out
EuiToolTip EuiPortal (always) Evaluate if portalling is necessary; add usePortal opt-out
EuiBottomBar EuiPortal (default on) Change default to usePortal={false}
EuiFlyout EuiPortal + EuiOverlayMask Support container-scoped flyouts; reduce portal usage for push type
EuiModal EuiOverlayMask โ†’ EuiPortal Evaluate container-scoped alternative
EuiDraggable EuiPortal (opt-in) No change needed (already opt-in)
EuiWrappingPopover EuiPortal (always) Evaluate if portalling is necessary
EuiImageFullScreenWrapper EuiOverlayMask May need to remain portalled
EuiCodeBlockFullScreen EuiOverlayMask May need to remain portalled
EuiDataGridCell createPortal Evaluate if portalling is necessary
_EuiPageBottomBar createPortal No change needed (portals to parent, not body)
DataGrid Draggable Columns EuiPortal No change needed (drag overlay only)

High-level approach for each component:

  1. Audit current portal usage and determine if it's necessary
  2. For components where portalling is unnecessary, remove it or change the default
  3. For components where portalling is sometimes needed, add a usePortal prop (if not present)
  4. For components where container-scoping is needed, add a container prop
  5. Migrate EuiPortal from class to function component ([EuiPortal] Migrate from class to function componentย #9456) to fix Strict Mode issues

Definition of Ready for Dev

  • PRD Ready
    • Link: {link}
  • Design Ready
    • Design issue: N/A (infrastructure change)
    • Design file: N/A
  • Estimated
  • Tasked

Definition of done

  • Kibana QA/preview completed (if needed)
  • Changes made in EUI
    • PRs: {link}
  • EUI, Figma, and Documentation updates published.
    • Figma: N/A
    • EUI: {link}
    • Documentation: {link}
  • Request issues resolved and stakeholders informed of release.
    • Links: {link}
  • Changes applied to Kibana
    • Links: {link}
  • EUI Beta issue created (if applicable)
    • Link: {link}
  • EUI Deprecation schedule updated (if applicable)

Related Issues

Notable Related Issue Details

#9255

"Long term, consider what could be done about portaling generally in EUI, which is overused, unnecessary, and problematic."

This spike is investigating:

  • How often users experience portal-related bugs in Kibana
  • Short-term solutions (e.g., updating Kibana usage to usePortal={false})
  • Long-term improvements to portaling strategy in EUI

#9456

EuiPortal is being migrated from a class component to a function component:

  • File: components/portal/portal.tsx
  • Used internally by every overlay component (popover, modal, flyout, tooltip)
  • Straightforward conversion: mount/unmount logic maps to useEffect cleanup pattern

#8784

When Strict Mode is enabled, portal divs are created twice and the empty one isn't cleaned up:

  • Impacts all end-users
  • No known workaround
  • EuiPortal is used in: EuiPopover (512 uses in Kibana), EuiToolTip (856), EuiFlyout (350), EuiOverlayMask (16)

#7778

Request to allow EuiPortal to use different portal targets via React Context, instead of always using document.body. This would enable:

  • Multi-window Electron apps
  • Child windows opened via window.open
  • Scoped flyouts/modals within specific containers

Open Issues (High Priority)

# Title Updated
#9456 [EuiPortal] Migrate from class to function component Mar 12, 2026
#9255 [Spike] Investigate "[EuiBottomBar] Fixed position..." Feb 17, 2026
#9242 [EuiBottomBar] Fixed position shouldn't mount into a portal by default Dec 2, 2025
#8784 [Strict Mode] Redundant div is portalled and not cleaned up Dec 10, 2025
#8520 [EuiDataGrid][A11y] Column selector dragging accessibility improvements Nov 27, 2025
#7778 EuiPortal should be able to use different portal target than document.body Sep 22, 2025

Search results uncovering 50+ related issues


Complete Issue List (50 issues returned)

# Title State Updated
#9389 [EuiFlyout] type="push" throws NotFoundError on unmount during concurrent DOM operations Closed Mar 12, 2026
#9456 [EuiPortal] Migrate from class to function component Open Mar 12, 2026
#9255 [Spike] Investigate "[EuiBottomBar] Fixed position... #9242" Open Feb 17, 2026
#8820 [Meta][Kibana Workspace Chrome] Improvements for new Kibana layout Closed Feb 5, 2026
#4157 [EuiDataGrid] Header Popover does not re-compute the position in scrollable container Closed Dec 15, 2025
#8784 [Strict Mode] Redundant div is portalled and not cleaned up Open Dec 10, 2025
#9242 [EuiBottomBar] Fixed position shouldn't mount into a portal by default Open Dec 2, 2025
#8520 [EuiDataGrid][A11y] Column selector dragging accessibility improvements Open Nov 27, 2025
#8984 [EuiPopover] Expose repositionOnScroll in componentDefaults Closed Nov 5, 2025
#8989 [Flyout System] Render EuiOverlayMask for EuiFlyout only when necessary Closed Oct 15, 2025
#7778 EuiPortal should be able to use different portal target than document.body based on provided Context Open Sep 22, 2025
#8578 Deprecate Support for React 16 Closed Jul 21, 2025
#8269 Workaround for Chromium CSS mask-image bug breaks positioning for nested position:fixed content Closed Apr 3, 2025
#8190 [EUI+] Missing Utilities content Closed Mar 17, 2025
#8410 [EUI+] Fix typos Closed Feb 27, 2025
#8206 [Bug] Focusable Site Navigation in Flyouts After Recent Update Closed Dec 5, 2024
#6300 [META] Add Cypress cy.checkAxe() tests to components Closed Nov 19, 2024
#5685 [Meta][CSS-in-JS] Component conversions Closed Sep 23, 2024
#6596 Portal widget (EuiBottomBar, EuiPopover etc) does not work in Vite dev mode Closed Aug 26, 2024
#7879 [A11Y] Using nested EuiPopover in EuiModal: Pressing ESC in child component closes parent Closed Jun 24, 2024
#7219 Spike: Removing Data Grid from @elastic/eui Closed Jun 20, 2024
#5788 [EuiPopover] Background in dark mode isn't different from the main background Closed May 30, 2024
#4265 [EuiPopover] Should unmount immediately on parent unmount Closed May 23, 2024
#7280 Close modal on outside click Closed May 14, 2024
#6882 [EuiPortal] Shadow DOM Support for Portal-Based Components (e.g., Modal, Overlay Mask) Closed May 13, 2024
#6845 [EuiToolTip] Dom cannot be removed Closed May 13, 2024
#6732 Not working properly on react and react-dom v-18 Closed Apr 17, 2024
#6592 [EuiPopover] Scrolling an open popover down stretches page height Closed Mar 21, 2024
#5330 Guidelines for bottom bars Closed Feb 12, 2024
#6338 EuiModal scrolls to bottom of page when modal is open Closed Jan 30, 2024
#6337 (duplicate of #6338) Closed Jan 30, 2024
#6304 EuiModal scrolls to bottom of page when modal is open Closed Jan 30, 2024
#5370 [EuiMarkdown] Allow EuiFlyout instead of EuiModal Closed Dec 6, 2023
#6339 [EuiResizableContainer] Add support for onResizeStart and onResizeEnd callback props Closed Nov 14, 2023
#6225 [EuiPortal docs] Add a custom modal/flyout example that meets a11y requirements Closed Sep 27, 2023
#6241 [EuiResizableContainer] Resizable container misbehaves when using react-reverse-portal Closed Aug 31, 2023
#6199 [EuiResizableContainer] Resizable container misbehaves when using react-reverse-portal Closed Aug 31, 2023
#5859 Popover and ContextMenu do not support react 18 Closed May 11, 2023
#5656 [EuiBottomBar] document is not defined when using server rendering Closed Apr 26, 2023
#1101 [EuiContextMenu] Tabbing regressions Closed Mar 23, 2023
#4364 [EuiDataGrid] Columns resize infinite loop Closed Jan 24, 2023
#4872 [EuiComboBox] Combobox popover can be hidden behind EuiBottomBar Closed Dec 19, 2021
#4756 [EuiPopover] Popover is placed above header (z-index) Closed Nov 29, 2021
#2903 Scrollspy feature requested Closed Sep 24, 2021
#5169 EuiGlobalToastList became hidden behind EuiFlyout with EUI 7.14.1 Closed Sep 22, 2021
#1023 [EuiInputPopover] In fixed container (like flyout) has positioning issues Closed Sep 13, 2021
#4666 [Portals][Doc] Props table not available for Portals Closed Mar 29, 2021
#1381 [makeId] Makes it difficult for consumers to create Jest tests Closed Nov 11, 2020
#4122 [EuiFlyout] [EuiButtonGroup] Z-Index conflict when a Flyout occludes Toggle Buttons Closed Oct 8, 2020
#1877 EuiBottomBar needs a container prop similar to EuiPopover Closed Sep 20, 2020

Risks

  • Breaking changes โ€” Changing portal defaults may affect existing Kibana usage; requires careful deprecation path
  • Edge cases โ€” Some components may genuinely need portalling in certain contexts (e.g., deeply nested scroll containers)
  • Testing complexity โ€” Portal behavior is difficult to test; changes require extensive manual QA in Kibana

Metadata

Metadata

Assignees

Labels

Type

No fields configured for Epic.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions