@@ -494,10 +494,8 @@ export const setRootAriaHidden = (hidden = false) => {
494494
495495 if ( hidden ) {
496496 viewContainer . setAttribute ( 'aria-hidden' , 'true' ) ;
497- viewContainer . setAttribute ( 'inert' , '' ) ;
498497 } else {
499498 viewContainer . removeAttribute ( 'aria-hidden' ) ;
500- viewContainer . removeAttribute ( 'inert' ) ;
501499 }
502500} ;
503501
@@ -529,15 +527,37 @@ export const present = async <OverlayPresentOptions>(
529527 * focus traps.
530528 *
531529 * All other overlays should have focus traps to prevent
532- * the keyboard focus from leaving the overlay.
530+ * the keyboard focus from leaving the overlay unless
531+ * developers explicitly opt out (for example, sheet
532+ * modals that should permit background interaction).
533+ *
534+ * Note: Some apps move inline overlays to a specific container
535+ * during the willPresent lifecycle (e.g., React portals via
536+ * onWillPresent). Defer applying aria-hidden/inert to the app
537+ * root until after willPresent so we can detect where the
538+ * overlay is finally inserted. If the overlay is inside the
539+ * view container subtree, skip adding aria-hidden/inert there
540+ * to avoid disabling the overlay.
533541 */
534- if ( overlay . el . tagName !== 'ION-TOAST' ) {
535- setRootAriaHidden ( true ) ;
536- document . body . classList . add ( BACKDROP_NO_SCROLL ) ;
537- }
542+ const overlayEl = overlay . el as HTMLIonOverlayElement & { focusTrap ?: boolean ; showBackdrop ?: boolean } ;
543+ const shouldTrapFocus = overlayEl . tagName !== 'ION-TOAST' && overlayEl . focusTrap !== false ;
544+ // Only lock out root content when backdrop is active. Developers relying on showBackdrop=false
545+ // expect background interaction to remain enabled.
546+ const shouldLockRoot = shouldTrapFocus && overlayEl . showBackdrop !== false ;
538547
539548 overlay . presented = true ;
540549 overlay . willPresent . emit ( ) ;
550+
551+ if ( shouldLockRoot ) {
552+ const root = getAppRoot ( document ) ;
553+ const viewContainer = root . querySelector ( 'ion-router-outlet, #ion-view-container-root' ) ;
554+ const overlayInsideViewContainer = viewContainer ? viewContainer . contains ( overlayEl ) : false ;
555+
556+ if ( ! overlayInsideViewContainer ) {
557+ setRootAriaHidden ( true ) ;
558+ }
559+ document . body . classList . add ( BACKDROP_NO_SCROLL ) ;
560+ }
541561 overlay . willPresentShorthand ?. emit ( ) ;
542562
543563 const mode = getIonMode ( overlay ) ;
@@ -653,22 +673,28 @@ export const dismiss = async <OverlayDismissOptions>(
653673 * For accessibility, toasts lack focus traps and don't receive
654674 * `aria-hidden` on the root element when presented.
655675 *
656- * All other overlays use focus traps to keep keyboard focus
657- * within the overlay, setting `aria-hidden` on the root element
658- * to enhance accessibility.
659- *
660- * Therefore, we must remove `aria-hidden` from the root element
661- * when the last non-toast overlay is dismissed.
676+ * Overlays that opt into focus trapping set `aria-hidden`
677+ * on the root element to keep keyboard focus and pointer
678+ * events inside the overlay. We must remove `aria-hidden`
679+ * from the root element when the last focus-trapping overlay
680+ * is dismissed.
662681 */
663- const overlaysNotToast = presentedOverlays . filter ( ( o ) => o . tagName !== 'ION-TOAST' ) ;
664-
665- const lastOverlayNotToast = overlaysNotToast . length === 1 && overlaysNotToast [ 0 ] . id === overlay . el . id ;
682+ const overlaysLockingRoot = presentedOverlays . filter ( ( o ) => {
683+ const el = o as HTMLIonOverlayElement & { focusTrap ?: boolean ; showBackdrop ?: boolean } ;
684+ return el . tagName !== 'ION-TOAST' && el . focusTrap !== false && el . showBackdrop !== false ;
685+ } ) ;
686+ const overlayEl = overlay . el as HTMLIonOverlayElement & { focusTrap ?: boolean ; showBackdrop ?: boolean } ;
687+ const locksRoot =
688+ overlayEl . tagName !== 'ION-TOAST' && overlayEl . focusTrap !== false && overlayEl . showBackdrop !== false ;
666689
667690 /**
668- * If this is the last visible overlay that is not a toast
691+ * If this is the last visible overlay that is trapping focus
669692 * then we want to re-add the root to the accessibility tree.
670693 */
671- if ( lastOverlayNotToast ) {
694+ const lastOverlayTrappingFocus =
695+ locksRoot && overlaysLockingRoot . length === 1 && overlaysLockingRoot [ 0 ] . id === overlayEl . id ;
696+
697+ if ( lastOverlayTrappingFocus ) {
672698 setRootAriaHidden ( false ) ;
673699 document . body . classList . remove ( BACKDROP_NO_SCROLL ) ;
674700 }
0 commit comments