Skip to content

[EuiCallOut] Use resize observer instead of container queries#9727

Open
mgadewoll wants to merge 3 commits into
elastic:feat/callout-toast-ui-enhancementsfrom
mgadewoll:callout/refactor-container-queries
Open

[EuiCallOut] Use resize observer instead of container queries#9727
mgadewoll wants to merge 3 commits into
elastic:feat/callout-toast-ui-enhancementsfrom
mgadewoll:callout/refactor-container-queries

Conversation

@mgadewoll

@mgadewoll mgadewoll commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR is a follow-up to #9705, which updated EuiCallOut to use container queries to handle the responsive behavior of the action elements.

Because EuiCallOut is a somewhat general component that can be placed in different layout contexts, it might be placed in containers that have no intrinsic width which would lead the container query to collapse as it has no dimensions to align to (e.g. absolute positioned containers - like <EuiPopover> - or flex layouts with no width basis - like <EuiFlexItem grow={false}>

To prevent issues because of this, we instead use a minimal resize observer implementation that sets a data-layout attribute which CSS can target accordingly instead of the container query.

API Changes

⚪ No API changes

Screenshots

Before After
Screenshot 2026-06-19 at 09 17 38 Screenshot 2026-06-19 at 09 19 08

For Callouts in general there no visual changes expected.

Before After
Screenshot 2026-06-18 at 15 20 36 Screenshot 2026-06-18 at 15 20 22
Screenshot 2026-06-18 at 15 19 29 Screenshot 2026-06-18 at 15 19 33
Screenshot 2026-06-18 at 15 21 15 Screenshot 2026-06-18 at 15 21 19

Impact Assessment

Note: Most PRs should be tested in Kibana to help gauge their Impact before merging.

  • 🔴 Breaking changes — What will break? How many usages in Kibana/Cloud UI are impacted?
  • 💅 Visual changes — May impact style overrides; could require visual testing. Explain and estimate impact.
  • 🧪 Test impact — May break functional or snapshot tests (e.g., HTML structure, class names, default values).
  • 🔧 Hard to integrate — If changes require substantial updates to Kibana, please stage the changes and link them here.

Impact level: 🟢 None - internal change only (pre-rollout)

Release Readiness

  • Documentation: {link to docs page(s)}
  • Figma: {link to Figma or issue}
  • Migration guide: {steps or link, for breaking/visual changes or deprecations}
  • Adoption plan (new features): {link to issue/doc or outline who will integrate this and where}

QA instructions for reviewer

  • verify that the responsive behavior (storybook) behaves the same as with the previous container-query implementation (storybook)

Checklist before marking Ready for Review

Reviewer checklist

  • Approved Impact Assessment — Acceptable to merge given the consumer impact.
  • Approved Release Readiness — Docs, Figma, and migration info are sufficient to ship.

@mgadewoll mgadewoll self-assigned this Jun 18, 2026
@mgadewoll mgadewoll added the skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) label Jun 18, 2026
@elasticmachine

Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

cc @mgadewoll

@elasticmachine

Copy link
Copy Markdown
Collaborator

💚 Build Succeeded

cc @mgadewoll

@mgadewoll mgadewoll requested a review from acstll June 18, 2026 13:45
@mgadewoll mgadewoll marked this pull request as ready for review June 18, 2026 13:45
@mgadewoll mgadewoll requested a review from a team as a code owner June 18, 2026 13:45
@acstll

acstll commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

I'll be reviewing this later today!

@acstll acstll left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Container responsiveness works as expected. I know we discussed this offline but I don't think it hurts to express my disappointment with container queries again 🫠 (let's try and make some time to investigate in this regard in the near future).

I left a suggestion to simplify the useLayoutObserver hook for your consideration (I tested it locally). Also, I think it'd be worth considering adding a small unit test for the hook.

/* Uses resize observer to determine the container width/layout instead of native container queries,
because callouts can be placed in containers without defined size (absolute positioned, no-grow flex layout etc.)
where container queries would collapse by design instead of adjusting to the content dimensions. */
const panelRef = useLayoutObserver(size, ref);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[non-blocking] I have a suggestion for simplifying refs by combining them here and removing the logic from the hook (follow-up comment)

the import for useCombinedRefs from services is missing in this diff, the suggestion below is for illustrating, not directly committing 😛

Suggested change
const panelRef = useLayoutObserver(size, ref);
const layoutRef = useLayoutObserver(size);
const panelRef = useCombinedRefs([layoutRef, ref]);

Comment on lines +27 to +43
export const useLayoutObserver = (
size: EuiCallOutSize,
ref: React.ForwardedRef<HTMLDivElement>
): ((node: HTMLDivElement | null) => void) => {
const elementRef = useRef<HTMLDivElement | null>(null);
const forwardedRefRef = useRef(ref);
forwardedRefRef.current = ref;

const panelRef = useCallback((node: HTMLDivElement | null) => {
elementRef.current = node;
const fRef = forwardedRefRef.current;
if (typeof fRef === 'function') {
fRef(node);
} else if (fRef) {
(fRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
}
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

following up on the previous comment, by passing no ref you can remove the useCallback bit:

Suggested change
export const useLayoutObserver = (
size: EuiCallOutSize,
ref: React.ForwardedRef<HTMLDivElement>
): ((node: HTMLDivElement | null) => void) => {
const elementRef = useRef<HTMLDivElement | null>(null);
const forwardedRefRef = useRef(ref);
forwardedRefRef.current = ref;
const panelRef = useCallback((node: HTMLDivElement | null) => {
elementRef.current = node;
const fRef = forwardedRefRef.current;
if (typeof fRef === 'function') {
fRef(node);
} else if (fRef) {
(fRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
}
}, []);
export const useLayoutObserver = (
size: EuiCallOutSize
): RefObject<HTMLDivElement | null> => {
const elementRef = useRef<HTMLDivElement | null>(null);

return () => observer.disconnect();
}, [size]);

return panelRef;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if you apply the above:

Suggested change
return panelRef;
return elementRef;

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

Labels

skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants