TL;DR (Too Long; Didn’t Read... o básicamente: mucho texto, dame el resumen rápido)
Añadir tabindex="0" a un <div> o <span> lo hace enfocable por teclado, pero no le otorga una identidad (rol) semántica. Las tecnologías asistivas leerán el texto interior pero la persona usuaria no sabrá qué tipo de control interactivo es ni cómo accionarlo.
La crisis de los componentes interactivos agnósticos
Muchos desarrolladores construyen botones y enlaces usando <div> para evadir los estilos por defecto del navegador. Al aplicar un evento click y tabindex, resuelven el acceso de mouse y teclado básico, pero violan drásticamente la WCAG 4.1.2 Nombre, Función, Valor.
El patrón erróneo: Código Accesible Deficiente
El lector de pantalla llega al elemento a través del tabulador, pero al no existir un rol semántico, la persona usuaria percibe texto muerto que inexplicablemente robó el foco.
<!-- MAL: Foco sin propósito semántico -->
<div tabindex="0" onclick="openModal()" class="btn-custom">
Guardar Preferencias
</div>
La Solución Accesible: Código Recomendado
La opción A (y preferida) es utilizar HTML nativo. La opción B (deuda técnica justificada) es mapear manualmente el polifill semántico con WAI-ARIA.
<!-- BIEN: Uso de botones nativos -->
<button type="button" onclick="openModal()" class="btn-custom">
Guardar Preferencias
</button>
<!-- ACEPTABLE: Reconstrucción ARIA (Requiere JS para tecla Enter/Espacio) -->
<div tabindex="0" role="button" onkeydown="handleKey(event)" onclick="openModal()">
Guardar Preferencias
</div>Metodología de Verificación y Testing
Pruebas Manuales con Lectores de Pantalla
Navegar con Tab hasta el div con tabindex="0" pero sin rol. NVDA leerá el contenido de texto pero NO anunciará un tipo de control (ni "botón", ni "enlace"). La persona usuaria no sabrá cómo interactuar. Comparar con el <button> nativo donde se anuncia "Guardar Preferencias, botón".
Inspección del Árbol de Accesibilidad
Verificar en el panel Accessibility de Chrome DevTools que el Role del div enfocable sea generic (sin identidad) vs. button en la solución correcta.
Herramientas Automatizadas
Axe DevTools no detectará automáticamente este patrón si el div tiene contenido de texto (no es una violación técnica de ARIA). WAVE puede marcarlo como "focusable element without semantics". Se recomienda usar el linter eslint-plugin-jsx-a11y en proyectos React para detectar divs con handlers de clic sin roles.