ionic-framework icon indicating copy to clipboard operation
ionic-framework copied to clipboard

bug: Accessibility issue - ion-popover has aria-hidden error when dismissed

Open mitchfaulcon opened this issue 1 year ago • 17 comments

Prerequisites

Ionic Framework Version

v8.x

Current Behavior

ion-popover element in popover has invalid aria-hidden attribute.

This issue does not happen in Firefox browsers but both Chrome & Edge show the following error in dev tools console when the popover is clicked off to close it: Blocked aria-hidden on an element because its descendant retained focus. The focus must not be hidden from assistive technology users. Avoid using aria-hidden on a focused element or its ancestor. Consider using the inert attribute instead, which will also prevent focus. For more details, see the aria-hidden section of the WAI-ARIA specification at https://w3c.github.io/aria/#aria-hidden. Element with focus: ion-popover Ancestor with aria-hidden: <ion-popover>...</ion-popover>

We recently updated our project to ionic 8.4.0 to address the issue fixed in 29773 which did fix the aria-hidden error on the ion-backdrop element, but it's now present on the ion-popover element instead.

Expected Behavior

No aria-hidden error when closing popup

Steps to Reproduce

  1. On Chrome or Edge browser click on button to open popup
  2. Click on backdrop to dismiss popup
  3. Error thrown in dev tools console

Code Reproduction URL

https://stackblitz.com/edit/angular-t5gtgv

Ionic Info

Ionic: Ionic CLI : 7.1.5 Ionic Framework : @ionic/angular 8.4.0 @angular-devkit/build-angular : 18.2.10 @angular-devkit/schematics : 17.3.11 @angular/cli : 18.2.6 @ionic/angular-toolkit : 11.0.1

Utility: cordova-res : not installed globally native-run : not installed globally

System: NodeJS : v20.10.0 npm : 10.9.0 OS : Windows 10

Additional Information

No response

mitchfaulcon avatar Nov 25 '24 01:11 mitchfaulcon

Actually this happens in many more elements. I am on 8.4.0, and it happens in ion-page, ion-modal as well. Obviously on a page and on a modal there are buttons and other focusable elements. Whenever the component is dismissed, the browser emits this error (blocking aria-hidden, because descendant retains focus).

pkunszt avatar Nov 27 '24 14:11 pkunszt

@pkunszt do you have workaround to fix it? The error is really annoy cause it don't provide info for app development. And it seems only alert error in latest Chrome browser.

raykin avatar Dec 06 '24 10:12 raykin

I am having the same issue on almost all modals I open

WhoofWhoof avatar Dec 06 '24 11:12 WhoofWhoof

the same issue is related to the ion-loader as well. I sorted it out adding an html attribute in the "htmlAttributes" loadingOptions of the loadingCtrl.create(loadingOptions);

marshall86 avatar Dec 09 '24 15:12 marshall86

@marshall86 Can you provide an example?

ingbioservicios avatar Dec 09 '24 20:12 ingbioservicios

@marshall86 Can you provide an example?

i'm using ionic/angular 8.1.0 yet but I got that kind of warning only using ion-loading component. As a workaround, I found out that using the "htmlAttributes" inside the LoadingOptions that you pass to the loading controller create method. Here an example:

const loadingOptions: LoadingOptions = { cssClass: 'custom-loader', message: 'Please wait...', htmlAttributes: [{ 'prevent-aria-hidden-error': true }], backdropDismiss: false }; const loader = await this.loadingCtrl.create(loadingOptions);

marshall86 avatar Dec 10 '24 08:12 marshall86

I can confirm that this htmlAttribute prevents the error for the loading controller, and probably also works for other controllers. However, it still persists for any page i am navigating from. And there the aria-hidden is inserted by the framework. So this is a bug that should be fixed.

pkunszt avatar Dec 11 '24 12:12 pkunszt

This is also happening when navigating between pages in a stack using ionic framework with react. It happens because the link clicked to navigate retains focus until the next page is fully rendered, but ionic attempts to add aria-hidden to the prior page before that. Thus the initial page, which still has focus, is given aria-hidden, and it's blocked by the browser. This breaks accessibility, as all pages now appear "visible" at the same time, whether they are in the foreground or not. It also breaks out ability to test the app using Playwright, because Playwright (correctly) thinks all pages are visible all the time.

kristojorg avatar Jan 16 '25 10:01 kristojorg

FWIW I am now manually applying aria-hidden to pages that are backgrounded using this hook:

export function useIsHidden() {
  const location = useLocation();

  // Store the path that caused this page to initially mount
  const [initialPath] = useState(location.pathname);

  // Compare current path to initial path
  const isVisible = location.pathname === initialPath;

  return !isVisible;
}

kristojorg avatar Jan 16 '25 10:01 kristojorg

Also having this issue in ionic Angular V7 and V6, but only recently, its actually started occurring on older projects that have had no code changes made. It seems to be the result of recent ARIA spec updates as opposed to any new behaviour or changes in Ionic, This update means that any aria-hidden elements with descendents in focus, will have it's aria-hidden status ignored by the browser and an error log thrown.

Ionic team need to make a change to blur focus on any element before adding aria-hidden to parent containers.

document.activeElement .blur()

This isn't something that developers should be expected to fix its a fundamental framework issue. This should be done before, opening a modal, changing the nav stack and any other things such as alerts, loaders, that trigger aria-hidden.

They may want to store the previously focused element before launching a modal, so that once the modal closes ,the user returns to the focus of where they left off, or provide an interface for the developer to chose that.

rl-carno avatar Jan 17 '25 13:01 rl-carno

Are there any plans to fix this problem? This is very annoying in the console.

gasci avatar Jan 30 '25 11:01 gasci

Frankly, I never find satisfactory answers on Github forums. Pure waste of time.

cchamorro avatar Feb 04 '25 05:02 cchamorro

and, sometimes, we don't call controller.create(), so we cannot give the options for htmlAttributes. Like, ion-modal used with [isOpen]

sebastien-closs avatar Feb 17 '25 09:02 sebastien-closs

Here is a workaround while we wait for a fix:

if (process.env.NODE_ENV === 'development') {
  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      if (mutation.type === 'attributes' && mutation.attributeName === 'aria-hidden') {
        mutation.target.removeAttribute('aria-hidden')
      }
    })
    document.querySelectorAll('[aria-hidden]').forEach(el => {
      el.removeAttribute('aria-hidden')
    })
  })

  observer.observe(document.body, {
    attributes: true,
    childList: true,
    subtree: true,
    attributeFilter: ['aria-hidden']
  })
}

csicky avatar Mar 17 '25 11:03 csicky

This works for me

export abstract class AriaFocusFixer implements ViewWillLeave {

  public ionViewWillLeave(): void {
    const activeElement = document.activeElement as HTMLElement;
    activeElement?.blur();
  }

}

export class SomePage extends AriaFocusFixer implements OnInit {

  constructor() {
    super();
  }
}

foreinger avatar Mar 28 '25 10:03 foreinger

@foreinger nice, but i also get the error directly on the router outlet, ie. when a modal is shown. I added this fix to the app.component as well, but it does not seem to do anything. i still get in the console the aria problem. you see below that there is a menu i am using - your fix is there, but the router is of a component i have no control over.

Image

And of course it still happens with all other ionic internal components like the ion-loading element that i create using the LoadingController. Also there I have no control and cannot add this fix. So this is a workaround for my own components, which is nice, but still: the ionic team really needs to follow protocol and adhere to the new aria flow for this console bug to properly go away.

pkunszt avatar Mar 28 '25 14:03 pkunszt

@pkunszt Sorry for the late answer, indeed this fix doesn't work for overlay components like modal, popovers etc. But it works fine for pages involved in router navigation, just make sure that each page of your routes extends AriaFocusFixer.

For fixing overlay components, I moved the blur call to the constructor for triggering it not after the presentation of the component but just after the class instance creation. It works for me. But still, there is a drawback that I need to extend this class for every page, modal, or popover component.

export abstract class AriaFocusFixer {

  constructor() {
    AriaFocusFixer.blurActiveFocus();
  }

  static blurActiveFocus(): void {
    const activeElement = document.activeElement as HTMLElement;
    activeElement?.blur();
  }
}

For overlays without passing components inside I use blur between .create() and .present() calls.

  public async showAlert(): Promise<void> {
    const alert = await this.alertController.create();
    AriaFocusFixer.blurActiveFocus();
    await alert.present();
  }

foreinger avatar Apr 04 '25 09:04 foreinger

I am having same issue on navigation. (navCtrl.navigateRoot(url) works fine, but window view doesn't change when using navCtrl.navigateForward(url) or angular-router navigate() because of this warning).

zura156 avatar Jul 12 '25 12:07 zura156

Any progress here?

fabichacon avatar Jul 16 '25 11:07 fabichacon

Here is a workaround while we wait for a fix:

if (process.env.NODE_ENV === 'development') {
  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      if (mutation.type === 'attributes' && mutation.attributeName === 'aria-hidden') {
        mutation.target.removeAttribute('aria-hidden')
      }
    })
    document.querySelectorAll('[aria-hidden]').forEach(el => {
      el.removeAttribute('aria-hidden')
    })
  })

  observer.observe(document.body, {
    attributes: true,
    childList: true,
    subtree: true,
    attributeFilter: ['aria-hidden']
  })
}

thanks bro, just linked it in main.ts for temporary fix

aamsur avatar Aug 23 '25 16:08 aamsur

This fix is out now in 8.7.4 🚀

ShaneK avatar Sep 17 '25 16:09 ShaneK

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

ionitron-bot[bot] avatar Oct 17 '25 17:10 ionitron-bot[bot]