bug: Accessibility issue - ion-popover has aria-hidden error when dismissed
Prerequisites
- [X] I have read the Contributing Guidelines.
- [X] I agree to follow the Code of Conduct.
- [X] I have searched for existing issues that already report this problem, without success.
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
- On Chrome or Edge browser click on button to open popup
- Click on backdrop to dismiss popup
- 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
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 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.
I am having the same issue on almost all modals I open
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 Can you provide an example?
@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);
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.
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.
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;
}
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.
Are there any plans to fix this problem? This is very annoying in the console.
Frankly, I never find satisfactory answers on Github forums. Pure waste of time.
and, sometimes, we don't call controller.create(), so we cannot give the options for htmlAttributes. Like, ion-modal used with [isOpen]
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']
})
}
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 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.
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
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();
}
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).
Any progress here?
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
This fix is out now in 8.7.4 🚀
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.