Skip to content

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:

  1. User clicks a button to open the modal
  2. ng-bootstrap modifies the DOM:
    • Adds aria-hidden="true" to the root application element
    • Creates and displays the modal overlay
  3. Problem remains: The original button retains focus
  4. Conflict: The focused element now exists within an aria-hidden subtree
html
<!-- 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>

Solution 1: Basic Manual Blur (Good for Small Applications)

Manually remove focus before opening the modal:

typescript
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

Create a reusable service to handle blurring automatically:

typescript
// 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:

typescript
// 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:

typescript
// 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:

html
<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:

  1. Setting initial focus in the modal
  2. Trapping focus within the modal
  3. Restoring focus to appropriate elements after close

Alternative Approach: Generic Event Listener

(For non-Angular Bootstrap sites or mixed environments)

typescript
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:

  1. Optimal Solution: Implement a CustomModalService to automatically blur triggering elements
  2. Alternative Solutions:
    • Add manual blur() calls before opening modals
    • Use a blurOnClick directive on trigger elements
    • Set up event listeners during modal closure

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.