From c536dd8442b34ee9683d879d3ba6a0e269045473 Mon Sep 17 00:00:00 2001 From: Matyas Forian-Szabo Date: Fri, 19 Jun 2026 16:55:22 +0200 Subject: [PATCH] fix(ui-modal): restore Modal open/close transition animation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Modal mounted and unmounted instantly with no fade. Transition drives its enter/exit lifecycle on its single direct child, but that child was a ModalContext.Provider (no DOM node), so the dialog was never animated. Move the Provider to wrap Transition (v1 and v2) so the dialog/Mask is the transitioned element again. In v2 the Dialog was additionally gated with `open={open && !transitioning}`, so the window was not rendered during the transition and could not fade with the Mask. Pass `open` unconditionally (matching v1) so the window fades together with the Mask, and release focus from the Dialog at the start of the exit transition so focus is not retained inside the aria-hidden, fading subtree. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/ui-modal/src/Modal/v1/index.tsx | 60 ++++++++++++--------- packages/ui-modal/src/Modal/v2/README.md | 16 +++--- packages/ui-modal/src/Modal/v2/index.tsx | 69 +++++++++++++----------- 3 files changed, 78 insertions(+), 67 deletions(-) diff --git a/packages/ui-modal/src/Modal/v1/index.tsx b/packages/ui-modal/src/Modal/v1/index.tsx index 366541e29e..ec2e14026a 100644 --- a/packages/ui-modal/src/Modal/v1/index.tsx +++ b/packages/ui-modal/src/Modal/v1/index.tsx @@ -107,6 +107,14 @@ class Modal extends Component { componentDidUpdate(prevProps: ModalProps) { if (this.props.open !== prevProps.open) { this.setState({ transitioning: true, open: !!this.props.open }) + // When closing, release focus from the Dialog. + // The Dialog stays open so its content can fade out, but the + // Transition marks the exiting subtree aria-hidden -- keeping focus inside + // it would hide a focused element from assistive tech resulting in a + // console warning + if (prevProps.open && !this.props.open) { + this._content?.blur() + } } this.props.makeStyles?.() } @@ -284,31 +292,31 @@ class Modal extends Component { onOpen={this.handlePortalOpen} data-cid="Modal" > - + this.setState({ bodyScrollAriaLabel: txt }) + }} > - - this.setState({ bodyScrollAriaLabel: txt }) - }} + {constrain === 'parent' ? ( @@ -317,8 +325,8 @@ class Modal extends Component { ) : ( this.renderDialog(passthroughProps) )} - - + + ) } diff --git a/packages/ui-modal/src/Modal/v2/README.md b/packages/ui-modal/src/Modal/v2/README.md index 27e3f7d192..937d96f2a5 100644 --- a/packages/ui-modal/src/Modal/v2/README.md +++ b/packages/ui-modal/src/Modal/v2/README.md @@ -618,16 +618,12 @@ You can do this with the `insertAt` prop or a theme override: type: code --- + themeOverride={{ + Mask: { + zIndex: 555 + } + }} +> ``` diff --git a/packages/ui-modal/src/Modal/v2/index.tsx b/packages/ui-modal/src/Modal/v2/index.tsx index f301d08c3a..37950b4c2a 100644 --- a/packages/ui-modal/src/Modal/v2/index.tsx +++ b/packages/ui-modal/src/Modal/v2/index.tsx @@ -106,6 +106,14 @@ class Modal extends Component { componentDidUpdate(prevProps: ModalProps) { if (this.props.open !== prevProps.open) { this.setState({ transitioning: true, open: !!this.props.open }) + // When closing, release focus from the Dialog. + // The Dialog stays open so its content can fade out, but the + // Transition marks the exiting subtree aria-hidden -- keeping focus inside + // it would hide a focused element from assistive tech resulting in a + // console warning + if (prevProps.open && !this.props.open) { + this._content?.blur() + } } this.props.makeStyles?.() } @@ -205,8 +213,7 @@ class Modal extends Component { | keyof ModalPropsForTransition | 'constrain' | 'overflow' - >, - open?: boolean + > ) { const { onDismiss, @@ -225,7 +232,7 @@ class Modal extends Component { { onOpen={this.handlePortalOpen} data-cid="Modal" > - + this.setState({ bodyScrollAriaLabel: txt }) + }} > - - this.setState({ bodyScrollAriaLabel: txt }) - }} + {constrain === 'parent' ? ( - {this.renderDialog(passthroughProps, open)} + {this.renderDialog(passthroughProps)} ) : ( - this.renderDialog(passthroughProps, open) + this.renderDialog(passthroughProps) )} - - + + ) }