Focus Management in Modal e Overlay WordPress: Guida Tecnica

14 agosto 20267 minAccessibilità Web
In breveAI

Guida tecnica per implementare correttamente il focus management in modal e overlay WordPress: codice vanilla JS, integrazione con plugin comuni, testing e conformità WCAG 2.1.

Il problema del focus nei componenti modali

I modal e gli overlay rappresentano una delle sfide più complesse per l’accessibilità web. Quando un modal si apre, gli screen reader e la navigazione da tastiera devono essere correttamente gestiti per evitare che l’utente possa navigare verso contenuti nascosti dietro l’overlay.

Secondo i dati del WebAIM Million 2025, il 38% dei siti analizzati presenta problemi critici nella gestione del focus nei componenti dinamici. In WordPress, dove plugin e temi spesso implementano modal personalizzati per lightbox, form di contatto, cookie banner e popup vari, il problema è ancora più diffuso.

Gli errori più comuni includono:

  • Focus non spostato automaticamente all’apertura del modal
  • Possibilità di navigare con Tab verso elementi sotto l’overlay
  • Mancato ripristino del focus alla chiusura
  • Assenza di attributi ARIA per comunicare lo stato agli assistive technology
  • Impossibilità di chiudere il modal con Escape

WCAG 2.1 e i requisiti per i modal

Le Web Content Accessibility Guidelines 2.1 definiscono requisiti specifici per i componenti modali, principalmente sotto i criteri:

  • 2.1.1 Keyboard (Livello A): tutte le funzionalità devono essere accessibili da tastiera
  • 2.1.2 No Keyboard Trap (Livello A): il focus non deve rimanere intrappolato in modo permanente
  • 2.4.3 Focus Order (Livello A): l’ordine di navigazione deve essere logico
  • 4.1.2 Name, Role, Value (Livello A): i componenti devono comunicare il proprio ruolo

Per un modal, questo significa implementare un focus trap temporaneo che mantiene il focus all’interno del dialog fino alla chiusura, ma che può essere disattivato dall’utente.

Implementazione del focus trap in JavaScript vanilla

La gestione corretta del focus richiede una strategia precisa. Ecco un’implementazione completa e testata:

class ModalFocusManager {
  constructor(modalElement) {
    this.modal = modalElement;
    this.focusableSelectors = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
    this.previousActiveElement = null;
  }

  open() {
    // Salva il focus corrente
    this.previousActiveElement = document.activeElement;
    
    // Ottieni elementi focalizzabili
    this.focusableElements = this.modal.querySelectorAll(this.focusableSelectors);
    this.firstFocusable = this.focusableElements[0];
    this.lastFocusable = this.focusableElements[this.focusableElements.length - 1];

    // Imposta ARIA attributes
    this.modal.setAttribute('role', 'dialog');
    this.modal.setAttribute('aria-modal', 'true');
    
    // Nascondi contenuto esterno dagli screen reader
    this.hideExternalContent();

    // Sposta focus al primo elemento o al modal stesso
    if (this.firstFocusable) {
      this.firstFocusable.focus();
    } else {
      this.modal.setAttribute('tabindex', '-1');
      this.modal.focus();
    }

    // Aggiungi event listeners
    this.modal.addEventListener('keydown', this.handleKeyDown.bind(this));
    document.addEventListener('focus', this.handleFocusEscape.bind(this), true);
  }

  handleKeyDown(e) {
    // ESC chiude il modal
    if (e.key === 'Escape') {
      this.close();
      return;
    }

    // TAB cycling
    if (e.key === 'Tab') {
      if (e.shiftKey) {
        if (document.activeElement === this.firstFocusable) {
          e.preventDefault();
          this.lastFocusable.focus();
        }
      } else {
        if (document.activeElement === this.lastFocusable) {
          e.preventDefault();
          this.firstFocusable.focus();
        }
      }
    }
  }

  handleFocusEscape(e) {
    // Previeni focus su elementi esterni
    if (!this.modal.contains(e.target)) {
      e.stopPropagation();
      this.firstFocusable.focus();
    }
  }

  hideExternalContent() {
    // Nascondi contenuto principale dagli screen reader
    const mainContent = document.getElementById('main');
    if (mainContent) {
      mainContent.setAttribute('aria-hidden', 'true');
      this.ariaHiddenElement = mainContent;
    }
  }

  close() {
    // Ripristina ARIA
    if (this.ariaHiddenElement) {
      this.ariaHiddenElement.removeAttribute('aria-hidden');
    }

    // Rimuovi listeners
    this.modal.removeEventListener('keydown', this.handleKeyDown);
    document.removeEventListener('focus', this.handleFocusEscape, true);

    // Ripristina focus
    if (this.previousActiveElement && this.previousActiveElement.focus) {
      this.previousActiveElement.focus();
    }
  }
}

Questa implementazione gestisce tutti gli aspetti critici: trap del focus con Tab cycling, chiusura con Escape, gestione degli screen reader e ripristino del focus originale.

Integrazione nei temi WordPress

Per integrare il focus management in un tema WordPress, è necessario enqueue dello script e inizializzare correttamente i modal:

// functions.php
function enqueue_modal_focus_manager() {
  wp_enqueue_script(
    'modal-focus-manager',
    get_template_directory_uri() . '/js/modal-focus-manager.js',
    array(),
    '1.0.0',
    true
  );
}
add_action('wp_enqueue_scripts', 'enqueue_modal_focus_manager');

// Inizializzazione
document.addEventListener('DOMContentLoaded', function() {
  const modalTriggers = document.querySelectorAll('[data-modal-trigger]');
  
  modalTriggers.forEach(trigger => {
    trigger.addEventListener('click', function(e) {
      e.preventDefault();
      const modalId = this.getAttribute('data-modal-trigger');
      const modal = document.getElementById(modalId);
      
      if (modal) {
        const focusManager = new ModalFocusManager(modal);
        modal.classList.add('is-open');
        focusManager.open();
        
        // Gestione chiusura
        const closeButtons = modal.querySelectorAll('[data-modal-close]');
        closeButtons.forEach(btn => {
          btn.addEventListener('click', () => {
            modal.classList.remove('is-open');
            focusManager.close();
          });
        });
      }
    });
  });
});

Gestione dei plugin WordPress comuni

Molti plugin WordPress popolari non implementano correttamente il focus management. Ecco come migliorare alcuni casi comuni:

Contact Form 7

Contact Form 7 non gestisce il focus nei suoi modal personalizzati. Per aggiungere la gestione:

document.addEventListener('wpcf7submit', function(event) {
  const form = event.target;
  const responseOutput = form.querySelector('.wpcf7-response-output');
  
  if (responseOutput) {
    responseOutput.setAttribute('tabindex', '-1');
    responseOutput.setAttribute('role', 'alert');
    responseOutput.focus();
  }
}, false);

WooCommerce lightbox

Il lightbox delle immagini prodotto di WooCommerce necessita di focus trap. Puoi estendere la funzionalità esistente:

jQuery(document).on('click', '.woocommerce-product-gallery__trigger', function() {
  setTimeout(() => {
    const lightbox = document.querySelector('.pswp');
    if (lightbox && lightbox.classList.contains('pswp--open')) {
      const focusManager = new ModalFocusManager(lightbox);
      focusManager.open();
    }
  }, 100);
});

Testing e validazione

La validazione del focus management richiede test manuali e automatizzati:

Test manuali essenziali

  1. Navigazione completa con solo tastiera (Tab, Shift+Tab, Enter, Escape)
  2. Test con screen reader (NVDA su Windows, VoiceOver su macOS)
  3. Verifica del focus visibile su ogni elemento interattivo
  4. Controllo del ripristino del focus alla chiusura
  5. Test su dispositivi touch con keyboard fisica

Strumenti automatizzati

  • axe DevTools: identifica problemi di focus order e ARIA
  • Lighthouse: audit automatico con Chrome DevTools
  • Pa11y: testing automatizzato da CLI, integrabile in CI/CD
// Esempio pa11y per WordPress
const pa11y = require('pa11y');

async function testModal() {
  const results = await pa11y('https://tuo-sito.test/pagina-con-modal', {
    actions: [
      'click element button[data-modal-trigger]',
      'wait for element [role="dialog"] to be visible'
    ],
    standard: 'WCAG2AA'
  });
  
  console.log(results);
}

testModal();

Performance e best practices

L’implementazione del focus management deve essere performante anche su siti con molti modal:

  • Lazy initialization: inizializza ModalFocusManager solo quando necessario, non al caricamento pagina
  • Event delegation: usa un singolo listener per tutti i trigger invece di listener individuali
  • Debouncing: per modal che si aprono/chiudono rapidamente, implementa debouncing per evitare conflitti
  • Cleanup: rimuovi sempre i listener quando il modal viene distrutto

Per siti gestiti con AgencyPilot, consigliamo di implementare questi pattern in un plugin custom condiviso tra tutti i clienti, permettendo aggiornamenti centralizzati quando emergono nuovi requisiti di accessibilità.

FAQ

Quale libreria JavaScript è meglio usare per il focus trap in WordPress?

Le librerie più affidabili sono focus-trap (7KB minified) e a11y-dialog (4KB). Entrambe sono mantenute attivamente e coprono tutti i casi edge. Per progetti custom, l’implementazione vanilla mostrata in questo articolo è sufficiente e evita dipendenze. Se usi già React/Vue, considera focus-trap-react o vue-focus-lock.

Come gestire il focus in modal annidati?

I modal annidati richiedono uno stack di focus manager. Quando si apre un secondo modal, il primo deve essere messo in pausa ma non chiuso. Salva i focus manager in un array: quando chiudi il modal corrente, riattiva il precedente nello stack. Assicurati che aria-modal="true" sia presente solo sul modal attivo in cima allo stack.

Il focus trap funziona con i page builder come Elementor?

Elementor genera markup complesso e usa il proprio sistema di popup. Per gestire correttamente il focus, aggancia il tuo script all’evento elementor/popup/show. Elementor Pro include già gestione base del focus dall’aggiornamento 3.11, ma manca il trap completo. Puoi estendere la funzionalità con un custom script che intercetta i popup e applica ModalFocusManager al container generato.

Come testare il focus management con automated testing?

Usa Playwright o Cypress con comandi di navigazione tastiera. In Playwright: await page.keyboard.press('Tab') e verifica con await expect(page.locator('button')).toBeFocused(). Per screen reader, Guidepup permette testing automatizzato con VoiceOver e NVDA. Integra questi test nella CI per prevenire regressioni quando aggiorni plugin o temi.

Devo implementare focus management anche per i cookie banner?

Assolutamente sì. I cookie banner sono modal a tutti gli effetti e devono seguire le stesse regole WCAG. Anzi, essendo spesso la prima interazione dell’utente, sono ancora più critici. Sposta il focus al banner all’apertura, implementa keyboard navigation tra i pulsanti, e permetti chiusura con Escape se l’accettazione non è obbligatoria per legge. Plugin come Complianz hanno migliorato l’accessibilità nelle versioni recenti, ma verifica sempre manualmente.

Gestisci i siti WordPress dei tuoi clienti?

AgencyPilot ti dà report AI, uptime monitoring, backup e portale clienti in un’unica dashboard. Gratis per 3 siti.

Prova gratis
Leggi anche
Tutti gli articoli
Tutti gli articoli