← cd ../blog

Meniuri accesibile: 3 abordări în proiectul Tiflo

Meniuri accesibile: 3 abordări în proiectul Tiflo — imagine de copertă

În cadrul proiectului Tiflo, accesibilitatea nu este un „feature” opțional, ci fundamentul pe care construim. Provocarea a fost clară: cum oferim o experiență de navigare impecabilă pentru utilizatorii de screen readers și tastatură, păstrând în același timp flexibilitatea WordPress? Am testat trei abordări, iar astăzi le analizăm tehnic.

1. Cele trei arhitecturi

  • Kadence Minimalist (v1): Un child theme simplu care suprascrie template-urile header.php. Rapid, dar limitat în controlul DOM-ului.
  • Kadence Avansat (v3): Utilizarea modulelor PHP pentru a injecta logica ARIA direct în Walker_Nav_Menu.
  • Tiflo Story (Custom): Temă standalone bazată pe Material Design 3, fără framework-uri CSS, folosind 134 de design tokens.

2. Navigație Disclosure și WAI-ARIA

Pentru a respecta WAI-ARIA, nu putem folosi link-uri standard pentru submeniuri. Injectăm butoane de toggle:

// Injectare buton pentru submeniuri
const items = document.querySelectorAll('.menu-item-has-children');
items.forEach(item => {
  const link = item.querySelector('a');
  const btn = document.createElement('button');
  btn.setAttribute('aria-expanded', 'false');
  btn.innerHTML = '<span class="sr-only">Extinde</span>';
  item.insertBefore(btn, item.querySelector('.sub-menu'));
});

3. Focus Trap pentru Mobile Drawer

Într-un overlay mobil, focusul nu trebuie să scape în restul paginii. Implementarea vanilla JS este esențială pentru performanță:

function trapFocus(modal) {
  const focusables = modal.querySelectorAll('button, [href], input, select, textarea');
  const first = focusables[0];
  const last = focusables[focusables.length - 1];

  modal.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault(); last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault(); first.focus();
      }
    }
  });
}

4. Toggle Contrast și Prevenirea FOUC

Pentru a evita Flash of Unstyled Content, rulăm un script inline cu prioritate 1 în wp_head:

add_action('wp_head', function() {
    echo "<script>const c = localStorage.getItem('contrast') || 'default'; document.documentElement.dataset.contrast = c;</script>";
}, 1);

CSS-ul aferent asigură contrastul necesar:

html[data-contrast='yellow-black'] {
  --surface: #000;
  --on-surface: #FFFF00;
  /* Contrast ratio 19.56:1 */
}

5. Manipularea Tag-urilor cu WP_HTML_Tag_Processor

Folosim noul API din WordPress 6.2+ pentru a curăța link-urile externe:

$processor = new WP_HTML_Tag_Processor($content);
while ($processor->next_tag('a')) {
    if (is_external($processor->get_attribute('href'))) {
        $processor->set_attribute('aria-label', $processor->get_content() . ' (se deschide în tab nou)');
        $processor->set_attribute('target', '_blank');
        $processor->set_attribute('rel', 'noopener');
    }
}

6. Fix Viewport prin Output Buffering

Unele teme blochează zoom-ul. Corectăm acest aspect fără să modificăm fișierele sursă ale temei părinte:

add_action('wp_head', 'start_viewport_buffer', 0);
add_action('wp_head', 'end_viewport_buffer', 999);

function start_viewport_buffer() { ob_start(); }
function end_viewport_buffer() {
    $html = ob_get_clean();
    echo preg_replace('/user-scalable=no/', 'user-scalable=yes', $html);
}

Concluzie: Pentru proiecte cu cerințe severe de accesibilitate, abordarea Tiflo Story (Custom Theme) oferă cel mai mare control. Totuși, dacă ești blocat într-un child theme, tehnici precum WP_HTML_Tag_Processor și Output Buffering îți pot salva proiectul fără a compromite structura temei părinte.