Skip to content

Conversation

@weronikaolejniczak
Copy link
Contributor

@weronikaolejniczak weronikaolejniczak commented Jan 14, 2026

Summary

Important

This is a high-priority PR. It should be reviewed, tested and released as soon as possible.

  • Wrapped EuiFlyoutChild, EuiFlyoutMain and EuiFlyoutManaged with forwardRef and passed it to EuiFlyoutComponent.
  • Passed ref in EuiFlyout to EuiFlyoutChild and EuiFlyoutMain.
  • Added unit tests to make sure when session is used, ref is not null.

Why are we making this change?

Resolves #9313

When session flyouts are used, the focus doesn't properly move to the flyout content, thus creating a big accessibility issue and potentially breaking focus-reliant functionality on consumer side. The reason was flyout components (EuiFlyoutChild, EuiFlyoutMain etc.) weren't wrapped with forwardRef and EuiFlyout didn't pass the ref. It came back as null.

It has been reported internally by Kibana teams. Unblocks elastic/kibana#246719.

Screenshots #

In Discover

Before After
Screen recording
Screen.Recording.2026-01-15.at.12.38.54.mov
Screen.Recording.2026-01-15.at.12.31.05.mov
Description Navigation between documents doesn't work, focus doesn't move to the flyout content, arrow keys cause navigation only in the data grid Navigation between documents works, focus moves to the flyout content, you can navigate it with arrow keys along with data grid

Impact to users

🟢 This is a bug fix. It's not a breaking change. No changes needed on consumer side. No visual changes were made.

QA

Specific checklist

  • Storybook
    • navigate within the flyout with the keyboard
    • open the main flyout
    • open the child flyout
    • try resizing the flyout
    • resize the browser window, the flyout should switch between overlay and push as expected
    • make sure there's no regression compared to prod
  • Unified Doc Viewer focus management is correct and using arrow keys for navigating between documents works

General checklist

  • Browser QA
    • Checked in both light and dark modes
    • Checked in both MacOS and Windows high contrast modes
    • Checked in mobile
    • Checked in Chrome, Safari, Edge, and Firefox
    • Checked for accessibility including keyboard-only and screenreader modes
  • Docs site QA
  • Code quality checklist
  • Release checklist
    • A changelog entry exists and is marked appropriately
    • If applicable, added the breaking change issue label (and filled out the breaking change checklist)
    • If the changes unblock an issue in a different repo, smoke tested carefully (see Testing EUI features in Kibana ahead of time)
  • Designer checklist
    • If applicable, file an issue to update EUI's Figma library with any corresponding UI changes. (This is an internal repo, if you are external to Elastic, ask a maintainer to submit this request)

@weronikaolejniczak weronikaolejniczak self-assigned this Jan 14, 2026
ref
) => {
const flyoutId = useFlyoutId(id);
const [flyoutRef, setFlyoutRef] = useState<HTMLElement | null>(null);
Copy link
Contributor Author

@weronikaolejniczak weronikaolejniczak Jan 14, 2026

Choose a reason for hiding this comment

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

The reason we use useState here is because of useResizeObserver hook that accepts a DOM element (container) as an argument and uses useEffect to attach the observer, see:

useEffect(() => {
if (container != null) {
const observer = makeResizeObserver(container, ([entry]) => {
const { inlineSize, blockSize } = entry.borderBoxSize[0];
setSize({
width: inlineSize,
height: blockSize,
});
});

If we use useRef, flyoutRef.current is null during the initial render. That means when the component mounts and the ref is populated, useRef does not trigger a re-render and the useResizeObserver is not re-evaluated so the observer fails to attach to the element.

By using useState and "callback ref" pattern we basically ensure that the component will re-render and pass the actual el to the observer.

I memoized the ref array before passing to useCombinedRefs to minimize unnecessary updates.

@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

cc @weronikaolejniczak

@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

cc @weronikaolejniczak

@weronikaolejniczak weronikaolejniczak marked this pull request as ready for review January 15, 2026 12:01
@weronikaolejniczak weronikaolejniczak requested a review from a team as a code owner January 15, 2026 12:01
});
});

describe('ref forwarding', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding tests! 🙏

@mgadewoll
Copy link
Contributor

[no change request, unrelated to the changes]

ℹ️ While testing I did encounter an unexpected behavior but it's unrelated to the changes in this PR as it's reproducible also on production.
When a child flyout is open and if the flyout is resizable the content container does not update accordingly on smaller viewports. Instead of tracking the visible (child) flyout width, it's still based on the underlying main flyout width.

Screen.Recording.2026-01-15.at.16.31.14.mov

Copy link
Contributor

@mgadewoll mgadewoll left a comment

Choose a reason for hiding this comment

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

🟢 All changes are LGTM.
The flyout functionality works as expected and there is no regression with production.
Thanks for tackling this bug fix! 💪

@weronikaolejniczak
Copy link
Contributor Author

weronikaolejniczak commented Jan 16, 2026

@mgadewoll That's a very good catch, I was able to reproduce.

The reason why this happens is when the flyouts are stacked (because of small viewport), the main flyout (push type) continues to push the main content based on its own width, even if it's underneath the child flyout and not visible. That child flyout width is different (especially if resized) so it yields that content offset.

The fix is very simple, it's just using the child flyout's width for the push padding calculation when in stacked layout mode and it's the main flyout. I have the code prepared and it seems to be working well:

Kapture.2026-01-16.at.10.23.06.mp4

It's not urgent while this PR is, so I'll push it on a separate one.

Thank you for the fast review, Lene! 🙏🏻

EDIT: #9322

@weronikaolejniczak weronikaolejniczak merged commit 8941687 into elastic:main Jan 16, 2026
6 checks passed
tkajtoch added a commit to elastic/kibana that referenced this pull request Jan 20, 2026
## Dependency updates

- `@elastic/eui`: `v111.1.0` ⏩ `v112.0.0`
- `@elastic/eslint-plugin-eui`: `v2.6.0` ⏩ `v2.7.0`

---

## Package updates

### `@elastic/eui`
[v112.0.0](https://github.com/elastic/eui/releases/tag/v112.0.0)

- Added `productDiscover` icon
([#9311](elastic/eui#9311))
- Updated `chartGauge` icon glyph
([#9311](elastic/eui#9311))
- Updated icon glyphs `endpoint` `eraser` `errorFill` `error` `eyeSlash`
`faceHappy` `faceNeutral` `faceSad` `folder` `fullScreenExit`
`fullScreen` `gradient` `grid` `heart` `home` `if` `image` `infinity`
`inputOutput` `key` `keyboard` `lineBreakSlash` `lineBreak` `lineDash`
`lineDot` `lineSolid` `logOut` `magnifyMinus` `magnifyPlus` `magnify`
`mail` `map` `mapping` `menuLeft` `menuRight` `menu` `merge`
`minusCircle` `minusSquare` `minus` `money` `moon` `move` `nested`
`number` `package` `paintBucket` `palette` `paperClip` `partial`
`pattern` `pause` `pencil` `percent` `pinFill` `pin` `pivot`
`plusCircle` `plusSquare` `plus` `popper` `presentation` `processor`
`productStreamsWired` `queryField` `queryOperand` `querySelector`
`queryValue` `query` `question` `quote` `radar` `readOnly` `redo`
`reporter` `return` `rocket` `scissors` `send` `shard` `share`
`snowflake` `sortAscending` `sortDescending` `starFill` `star` `stop`
`sun` `tableInfo` `tableTime` `textAlignCenter` `textAlignLeft`
`textAlignRight` `textBold` `textHeading` `textItalic` `textStrike`
`textUnderline` `thermometer` `thumbDown` `thumbUp` `timeline`
`transitionLeftIn` `transitionLeftOut` `transitionTopIn`
`transitionTopOut` `undo` `vectorSquare` `vectorTriangle` `videoPlayer`
`warningFill` `waypoint` `wifiSlash` `wifi`
([#9303](elastic/eui#9303))
 ([#9303](elastic/eui#9303))
- Added icons - `archive` `unarchive` `axisX` `axisYLeft` `axisYRight`
`bulb` `cloud` `hourglass` `megaphone` `workflow`
([#9303](elastic/eui#9303))
- Added `headerVisibility` prop on `EuiDataGrid` to support rendering
the datagrid header element optionally
([#9281](elastic/eui#9281))
- Updated 244 icon definitions to a more consistent naming convention.
All 100 renamed icons include a backward-compatible alias in the icon
map to support legacy references.
([#9279](elastic/eui#9279))
- Added icons `briefcase`, `productCloudInfra`, `productDashboard`,
`productML` ([#9301](elastic/eui#9301))
- Updated glyphs `bullseye`, `bolt`
([#9301](elastic/eui#9301))
- Added `dismissButtonProps` prop to `EuiCallOut`
([#9285](elastic/eui#9285))

**Bug fixes**

- Fixed `EuiFlyout` to properly forward refs when `session` prop is
used. ([#9318](elastic/eui#9318))
- Fixed `EuiDataGrid` cells scrolling into view while trying to select
text ([#9276](elastic/eui#9276))

**Breaking changes**

- Removed `euiPaletteForLightBackground` and
`euiPaletteForDarkBackground` deprecated palette functions. Use
`euiTheme.colors.vis.euiColorVisText{NUMBER}` tokens instead.
([#9296](elastic/eui#9296))

**Accessibility**

- Improved the accessibility of `EuiDataGrid`s column selector drag
handle buttons by ensuring distinctive labels
([#9282](elastic/eui#9282))

### `@elastic/eslint-plugin-eui` v2.7.0

- Added `no-static-z-index` rule
([#9236](elastic/eui#9236))
dennis-tismenko pushed a commit to dennis-tismenko/kibana that referenced this pull request Jan 22, 2026
## Dependency updates

- `@elastic/eui`: `v111.1.0` ⏩ `v112.0.0`
- `@elastic/eslint-plugin-eui`: `v2.6.0` ⏩ `v2.7.0`

---

## Package updates

### `@elastic/eui`
[v112.0.0](https://github.com/elastic/eui/releases/tag/v112.0.0)

- Added `productDiscover` icon
([elastic#9311](elastic/eui#9311))
- Updated `chartGauge` icon glyph
([elastic#9311](elastic/eui#9311))
- Updated icon glyphs `endpoint` `eraser` `errorFill` `error` `eyeSlash`
`faceHappy` `faceNeutral` `faceSad` `folder` `fullScreenExit`
`fullScreen` `gradient` `grid` `heart` `home` `if` `image` `infinity`
`inputOutput` `key` `keyboard` `lineBreakSlash` `lineBreak` `lineDash`
`lineDot` `lineSolid` `logOut` `magnifyMinus` `magnifyPlus` `magnify`
`mail` `map` `mapping` `menuLeft` `menuRight` `menu` `merge`
`minusCircle` `minusSquare` `minus` `money` `moon` `move` `nested`
`number` `package` `paintBucket` `palette` `paperClip` `partial`
`pattern` `pause` `pencil` `percent` `pinFill` `pin` `pivot`
`plusCircle` `plusSquare` `plus` `popper` `presentation` `processor`
`productStreamsWired` `queryField` `queryOperand` `querySelector`
`queryValue` `query` `question` `quote` `radar` `readOnly` `redo`
`reporter` `return` `rocket` `scissors` `send` `shard` `share`
`snowflake` `sortAscending` `sortDescending` `starFill` `star` `stop`
`sun` `tableInfo` `tableTime` `textAlignCenter` `textAlignLeft`
`textAlignRight` `textBold` `textHeading` `textItalic` `textStrike`
`textUnderline` `thermometer` `thumbDown` `thumbUp` `timeline`
`transitionLeftIn` `transitionLeftOut` `transitionTopIn`
`transitionTopOut` `undo` `vectorSquare` `vectorTriangle` `videoPlayer`
`warningFill` `waypoint` `wifiSlash` `wifi`
([elastic#9303](elastic/eui#9303))
 ([elastic#9303](elastic/eui#9303))
- Added icons - `archive` `unarchive` `axisX` `axisYLeft` `axisYRight`
`bulb` `cloud` `hourglass` `megaphone` `workflow`
([elastic#9303](elastic/eui#9303))
- Added `headerVisibility` prop on `EuiDataGrid` to support rendering
the datagrid header element optionally
([elastic#9281](elastic/eui#9281))
- Updated 244 icon definitions to a more consistent naming convention.
All 100 renamed icons include a backward-compatible alias in the icon
map to support legacy references.
([elastic#9279](elastic/eui#9279))
- Added icons `briefcase`, `productCloudInfra`, `productDashboard`,
`productML` ([elastic#9301](elastic/eui#9301))
- Updated glyphs `bullseye`, `bolt`
([elastic#9301](elastic/eui#9301))
- Added `dismissButtonProps` prop to `EuiCallOut`
([elastic#9285](elastic/eui#9285))

**Bug fixes**

- Fixed `EuiFlyout` to properly forward refs when `session` prop is
used. ([elastic#9318](elastic/eui#9318))
- Fixed `EuiDataGrid` cells scrolling into view while trying to select
text ([elastic#9276](elastic/eui#9276))

**Breaking changes**

- Removed `euiPaletteForLightBackground` and
`euiPaletteForDarkBackground` deprecated palette functions. Use
`euiTheme.colors.vis.euiColorVisText{NUMBER}` tokens instead.
([elastic#9296](elastic/eui#9296))

**Accessibility**

- Improved the accessibility of `EuiDataGrid`s column selector drag
handle buttons by ensuring distinctive labels
([elastic#9282](elastic/eui#9282))

### `@elastic/eslint-plugin-eui` v2.7.0

- Added `no-static-z-index` rule
([elastic#9236](elastic/eui#9236))
weronikaolejniczak added a commit to weronikaolejniczak/eui that referenced this pull request Jan 23, 2026
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.

[EuiFlyout] When using session flyouts, ref is null

3 participants