-
Notifications
You must be signed in to change notification settings - Fork 859
Description
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:
-
UI Crashes โ Push flyouts throw
NotFoundErrorduring 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. -
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.
-
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). -
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).
-
SSR Incompatibility โ Portal components fail during server-side rendering because
documentis 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:
- Audit current portal usage and determine if it's necessary
- For components where portalling is unnecessary, remove it or change the default
- For components where portalling is sometimes needed, add a
usePortalprop (if not present) - For components where container-scoping is needed, add a
containerprop - Migrate
EuiPortalfrom 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
"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
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
useEffectcleanup pattern
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)
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