Fix 'Blocked aria-hidden' Warning in Angular Modals
Problem Statement
When using Angular with ng-bootstrap modals, you might encounter this accessibility warning:
"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."
This occurs when:
- Your modal dialog opens
aria-hidden="true"
is applied to elements in the background- The focus remains on the element that triggered the modal (e.g., a button)
The core accessibility issue: Elements with aria-hidden
shouldn't contain focused elements, as this violates WCAG guidelines and creates problems for users relying on assistive technologies.
Understanding the Root Cause
The Angular Bootstrap modal workflow:
- User clicks a button to open the modal
- ng-bootstrap modifies the DOM:
- Adds
aria-hidden="true"
to the root application element - Creates and displays the modal overlay
- Adds
- Problem remains: The original button retains focus
- Conflict: The focused element now exists within an
aria-hidden
subtree
<!-- Problem scenario -->
<div app-root aria-hidden="true"> <!-- Root element now hidden -->
...
<!-- This button still retains focus -->
<button (click)="openModal()">Open Modal (focused)</button>
...
</div>
Recommended Solutions
Solution 1: Basic Manual Blur (Good for Small Applications)
Manually remove focus before opening the modal:
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
openModal() {
// Get reference to the active element
const activeElement = document.activeElement as HTMLElement;
// Remove focus from active element
activeElement.blur();
// Open modal
this.modalService.open(ModalComponent);
}
Pros:
- Simple to implement
- Requires no additional services
Cons:
- Not scalable (requires adding to every modal trigger)
- TypeScript may warn about possible
null
Solution 2: Custom Modal Service (Recommended for Larger Applications)
Create a reusable service to handle blurring automatically:
// custom-modal.service.ts
import { Injectable } from '@angular/core';
import { NgbModal, NgbModalOptions, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
@Injectable({ providedIn: 'root' })
export class CustomModalService extends NgbModal {
override open(content: any, options?: NgbModalOptions): NgbModalRef {
// Reference to active element
const activeElement = document.activeElement as HTMLElement;
// Remove focus if element exists
activeElement?.blur();
// Proceed with modal opening
return super.open(content, options);
}
}
Register in app.module.ts
:
// app.module.ts
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CustomModalService } from './custom-modal.service';
@NgModule({
providers: [
{ provide: NgbModal, useClass: CustomModalService }
]
})
export class AppModule {}
Benefits:
- Automatic solution for all modals
- Follows Angular DI best practices
- Prevents duplicate code
- Maintains original modal API
Solution 3: Blur Directive for Trigger Elements
⚠️ Use directive only when you cannot modify modal opening logic
Create a directive to blur elements when clicked:
// blur-on-click.directive.ts
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[appBlurOnClick]',
standalone: true
})
export class BlurOnClickDirective {
constructor(private el: ElementRef) {}
@HostListener('click')
onClick() {
this.el.nativeElement.blur();
}
}
Apply to modal triggers:
<button
appBlurOnClick
(click)="openModal()">
Open Modal
</button>
Why These Solutions Work
Every solution follows the same principle: Remove focus from the triggering element before the modal opens. This prevents violation of accessibility rules requiring:
- Hidden elements (
aria-hidden="true"
) shouldn't contain focusable children - Assistive technology must maintain accurate contextual focus
- Focus should be programmatically managed within dialogues
Best Practice Tip
Focus management should be handled programmatically in accessibility-compliant modals. Implement these solutions alongside:
- Setting initial focus in the modal
- Trapping focus within the modal
- Restoring focus to appropriate elements after close
Alternative Approach: Generic Event Listener
(For non-Angular Bootstrap sites or mixed environments)
document.addEventListener('hide.bs.modal', () => {
if (document.activeElement) {
(document.activeElement as HTMLElement).blur();
}
});
Conclusion
The "Blocked aria-hidden" warning results from retained focus on triggering elements. To resolve it:
- Optimal Solution: Implement a
CustomModalService
to automatically blur triggering elements - Alternative Solutions:
- Add manual
blur()
calls before opening modals - Use a
blurOnClick
directive on trigger elements - Set up event listeners during modal closure
- Add manual
These approaches ensure your modals comply with accessibility standards while preventing the browser warning. Since ng-bootstrap currently uses aria-hidden
background approach (not inert
), focus management remains essential for accessibility compliance.
Pro Tip: Test your solutions using screen readers (NVDA, VoiceOver) and browser accessibility audits to verify full compliance.