Addaw, ir a inicio Donar

Lección 10 de 10 · Caso práctico · 25 min · Revisado el 26/10/2026

Caso práctico, ejercicios y lista de comprobación

Esta lección reúne todo el curso en un caso realista. Primero resolvemos paso a paso la revisión de un componente con problemas de ARIA; después practicas con ejercicios de autoevaluación y terminas con una lista de comprobación que puedes usar en tu trabajo.

El caso: la cita previa de una sede electrónica

Un ayuntamiento ha renovado el formulario de cita previa de su sede electrónica. La persona elige oficina, filtra los huecos disponibles y confirma la cita. Una vecina que usa NVDA con Firefox ha enviado una queja a través del mecanismo de comunicación que exige el RD 1112/2018 (PDF, se abre en una ventana nueva): «No puedo abrir los filtros, no sé cuántas citas hay y, después de cerrar el aviso de la cookie, la página se queda muda». Este es el fragmento relevante:

<div class="btn-filtros" role="button" onclick="toggleFiltros()">Filtros</div>
<div id="filtros" class="oculto">
  <span class="switch" onclick="toggleTardes(this)">Solo tardes</span>
</div>
<p class="contador">12 huecos disponibles</p>
<div role="option" class="hueco" onclick="elegir(this)">Lunes 3, 17:00</div>
<div role="option" class="hueco" onclick="elegir(this)">Lunes 3, 17:30</div>
<button aria-label="Close" onclick="cerrarAviso()"><svg>…</svg></button>
<button disabled>Confirmar cita</button>
<main aria-hidden="true">…</main> <!-- estado tras cerrar el aviso -->

Paso 1: inventario

Busca en el código todas las apariciones de role= y aria-, y todos los controles construidos con elementos genéricos y onclick. Aquí hay seis piezas que revisar: el botón de filtros, el interruptor, el contador, las opciones de hora, el botón de cerrar el aviso y el botón de confirmar, más el estado de <main> tras cerrar el aviso.

Paso 2: inspeccionar el árbol y escuchar

Con el panel Accesibilidad del navegador y NVDA (con el Visor de voz activo), recorre el formulario con Tab y con el modo exploración. Hallazgos:

  • El div de filtros se anuncia como botón en modo exploración, pero Tab nunca llega a él y no responde a Intro (reglas 1 y 3). Tampoco comunica si el panel está abierto.
  • El interruptor es un span sin rol ni estado: el lector dice «Solo tardes» como texto plano (4.1.2).
  • Al filtrar, el contador cambia de 12 a 4 sin anunciarse (4.1.3).
  • Las opciones están sueltas, sin listbox que las contenga ni teclado, y no indican cuál está elegida.
  • El botón de cerrar se llama «Close» en una página en español, y el icono no está oculto.
  • «Confirmar cita» está deshabilitado y nadie que navegue con teclado sabe por qué.
  • <main> conserva aria-hidden="true" tras cerrar el aviso: todo el contenido ha desaparecido para el lector, aunque el foco sigue llegando a los controles.

Paso 3: corregir, de lo más grave a lo menos

Primero lo que bloquea por completo: el contenido oculto. El aviso se rehace con inert (o con <dialog>), y se comprueba que al cerrarlo se restaura el estado. Después, los controles: HTML nativo siempre que existe y ARIA solo para lo que HTML no expresa. Por último, los anuncios.

<button type="button" aria-expanded="false" aria-controls="filtros">Filtros</button>
<div id="filtros" hidden>
  <button type="button" role="switch" aria-checked="false">Solo tardes</button>
</div>
<p role="status" id="contador"></p>
<fieldset>
  <legend>Hora de la cita</legend>
  <label><input type="radio" name="hora" value="1700"> Lunes 3, 17:00</label>
  <label><input type="radio" name="hora" value="1730"> Lunes 3, 17:30</label>
</fieldset>
<button type="button" aria-label="Cerrar aviso"><svg aria-hidden="true">…</svg></button>
<button type="submit" aria-disabled="true" aria-describedby="motivo">Confirmar cita</button>
<p id="motivo">Elige una hora para continuar.</p>

Decisiones explicadas:

  • Las opciones de hora no necesitaban un listbox: son una elección única entre varias, y los botones de opción nativos dentro de un fieldset dan rol, estado, agrupación y teclado sin una línea de ARIA (regla 1).
  • El interruptor sí usa ARIA, porque HTML no tiene un interruptor nativo: role="switch" sobre un <button>, con aria-checked actualizado por JavaScript y el CSS dependiendo del atributo.
  • El contador es una región status que existe vacía al cargar; el script escribe «4 huecos disponibles» tras cada filtro.
  • El nombre «Cerrar aviso» está en español y coincide con lo que hace (2.5.3 no aplica porque no hay texto visible, pero el idioma sí importa).
  • «Confirmar cita» se mantiene descubrible con aria-disabled y explica el motivo; el script bloquea el envío mientras falte la hora. Una alternativa igual de válida es dejarlo activo y mostrar un error al pulsarlo.

Paso 4: verificar

Se repite el recorrido con NVDA y Firefox y con VoiceOver y Safari: «Filtros, botón, contraído» y «expandido» al pulsar; «Solo tardes, interruptor, desactivado»; «4 huecos disponibles» sin mover el foco; las horas como grupo de botones de opción; «Confirmar cita, botón, no disponible, Elige una hora para continuar». Se pasan axe y el validador del W3C para confirmar que no quedan referencias rotas ni atributos inválidos, y se responde a la vecina con la corrección realizada.

Ejercicios de autoevaluación

1. Una casilla de aceptación está hecha así: <div role="checkbox" aria-checked="false" tabindex="0">Acepto las condiciones</div>. ¿Qué le falta y cuál es la mejor solución?

Ver solución

Le falta el comportamiento: responder a Espacio y actualizar aria-checked en cada cambio, además de participar en el envío del formulario. La mejor solución es la regla 1: <input type="checkbox"> con su <label>, que da todo eso sin ARIA.

2. En el enlace «Ver todos los trámites →» el lector anuncia «flecha derecha». ¿Cómo lo evitas sin cambiar el aspecto?

Ver solución

Envuelve la flecha en un <span aria-hidden="true">. El enlace sigue siendo enfocable y conserva el nombre «Ver todos los trámites»; solo se retira del árbol el adorno.

3. Un listado de resultados virtualizado muestra 20 de 300 elementos y el lector anuncia «1 de 20» en el resultado número 141. ¿Qué atributos lo corrigen?

Ver solución

aria-setsize="300" y aria-posinset="141" (y sucesivos) en cada elemento de la lista, junto con un texto visible del tipo «Mostrando 141-160 de 300». Comprueba el soporte con los lectores de tu público.

4. Una intranet pone role="application" en el <body> para que funcionen sus atajos de una sola tecla. ¿Qué consecuencia tiene y qué propones?

Ver solución

El lector deja de interceptar el teclado en toda la página: la persona no puede leer con flechas ni saltar por encabezados. Hay que quitar el rol y revisar los atajos para que puedan desactivarse o reasignarse, o actúen solo con el foco en el componente, como exige 2.1.4 Atajos de teclado de un solo carácter.

5. La navegación principal de un portal usa <ul role="menu"> con role="menuitem" en cada enlace. ¿Qué cambiarías?

Ver solución

Quitaría los roles menu y menuitem, pensados para menús de aplicación operados con flechas, y usaría <nav aria-label="Principal"> con una lista de enlaces normales, marcando la página actual con aria-current="page".

Lista de comprobación

  • He usado HTML nativo siempre que existía un elemento equivalente.
  • No hay roles redundantes, inválidos ni abstractos.
  • Cada rol de widget tiene foco y el teclado que describe la APG (se abre en una ventana nueva).
  • Cada rol respeta su contexto y sus hijos obligatorios.
  • Todos los controles tienen un nombre accesible que contiene el texto visible.
  • Cada estado visual tiene su atributo ARIA, y JavaScript lo actualiza.
  • Todas las referencias por id existen y son únicas.
  • No hay aria-hidden="true" sobre elementos enfocables ni en <body>.
  • Las zonas fuera de uso usan inert, y se restauran al terminar.
  • Los controles con aria-disabled bloquean la acción y explican el motivo.
  • Los mensajes de estado se escriben en regiones status o alert que ya existían vacías.
  • No hay role="application" salvo en superficies con teclado propio completo.
  • He pasado axe y el validador del W3C sin errores de ARIA.
  • He escuchado cada componente con al menos dos lectores de pantalla.

Para seguir aprendiendo

Comprueba lo que has aprendido

Esta lección se puede leer libremente. Para hacer su test de 3 preguntas, los ejercicios prácticos con páginas reales, guardar tu avance y optar al examen y al certificado, matricúlate en el curso.