Rediseño accesible: analizador financiero - Parte 1: HTML (Componentes)

Publicado el:

Este es el primero de tres artículos en los que cubro el rediseño de uno de mis proyectos con el fin de hacerlo más accesible. Esta parte estará enfocada en la estructura de HTML de los componentes principales del proyecto.


Introducción (Link permanente)

Esta es la primera parte de una serie de artículos que cubren el rediseño de este analizador financiero⁠ (Abre en una nueva pestaña) con el fin de hacerlo más accesible. Pueden ver todo el proceso en este artículo recopilatorio.

Uno de los elementos cruciales para que un sitio sea accesible es tener en mente una estructura de HTML sólida. Esto tiene varios fines como hacer el sitio mucho más fácil de navegar para gente que depende de lectores de pantalla para navegar por un sitio, para las personas que por alguna dificultad motriz dependan de navegar con el teclado, para personas que usan el modo de alto contraste de Windows (que por cierto, tengo algo cocinándose sobre el tema ¡estén atentos! ) y varias razones más.

A veces sólo la semántica no es suficiente para hacer un sitio completamente accesible (aunque ayuda bastante) y tendremos que recurrir a múltiples estrategias que este lenguaje nos ofrece. En esta parte estaré revisando cada componente de mi proyecto y reestructurando su código HTML para mejorar la accesibilidad de este. En este artículo se tomarán temas como:

  • Consideraciones de semántica al momento de crear componentes.
  • aria roles.
  • aria properties.
  • live regions.
  • Elementos ocultos.

Sin más dilación, empecemos a revisar estos componentes:


Barra de progreso (Link permanente)

Vista de la pantalla inicial con la barra de progreso en la parte superior

Este analizador financiero tiene una barra de progreso en la parte de arriba que indica en qué parte del proceso está el usuario. Visualmente funciona bien pero para usuarios de lectores de pantalla, esto no les ofrecerá ningún contexto. Empecemos revisando que está mal con este componente.

Markup actual Link permanente

<div class="progress-bar">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

Si bien es algo netamente visual y por lo tanto no hay nada inherentemente malo con este acercamiento, una persona que no use un lector de pantalla no va a saber en qué parte del proceso se encuentra

¿Cómo arreglar este componente? Link permanente

Revisando algunas ideas, creo que el mejor modo sería usar un como este modelo que Andy Bell sugirió a modo de desafío⁠ (Abre en una nueva pestaña) . Veamos un poco el markup de este elemento:

<div class="progress-stepper">
  <h2 id="progressTitle">Pasos del análisis</h2>
  <ol role="list" aria-labelledby="progressTitle">
    <li aria-current="step">
      <b aria-hidden="true">Paso 1</b>
      <span>Activos</span>
    </li>
    <li>
      <b aria-hidden="true">Paso 2</b>
      <span>Pasivos y patrimonio</span>
    </li>
    <li>
      <b aria-hidden="true">Paso 3</b>
      <span>Ingresos, costos y gastos</span>
    </li>
    <li>
      <b aria-hidden="true">Paso 4</b>
      <span>Resumen</span>
    </li>
    <li>
      <b aria-hidden="true">Paso 5</b>
      <span>Reporte</span>
    </li>
  </ol>
</div>

Veamos como funciona este componente:

  • El mejor modo para mostrar al usuario en qué paso va es usar una lista ordenada con cada paso, por esto uso un ol.

  • El role="list" está aquí por un curioso bug que pasa en Safari: cuando agreguemos estilos a este elemento, muy seguramente usaremos la propiedad list-style: none para remover la numeración. Esto no remueve la semántica de este elemento en los lectores de pantalla, excepto en Safari donde VoiceOver (el lector de pantalla de Apple) deja de reconocerlo como lista. El role="list" es una capa extra de seguridad que evita que pase este bug.

  • Tenemos un h2 que tiene un id y nuestro ol tiene el atributo aria-labelledby con este id. Esto hace que nuestro h2 funcione como etiqueta del ol en lectores de pantalla, un lector de pantalla lo leerá de la siguiente manera.

    Pasos del análisis, lista con 5 elementos.

    Si el título visualmente no es necesario (y va a ser nuestro caso) podremos ocultarlo con CSS. También es posible no usar otro elemento y el atributo arialabelled-by para etiquetar nuestro ol y directamente usar un aria-label.

    Esto último es una buena opción, pero hay que tener en cuenta que un aria-label no va a ser traducido por el navegador (cosa que no pasa si usas un elemento HTML y aria-labelledby) lo cual podría generar problemas en el caso de que quieras que el contenido sea accesible por personas que hablen otros idiomas y quieran usar tu sitio.

  • Notarás que dentro de los li hay unos b con el atributo aria-hidden="true". Esto lo que hace es ocultarlos de lectores de pantalla pero no visualmente. Ya que el hecho de estar en una lista ordenada es suficiente indicador para quienes usan un lector de pantalla, podremos usar estos indicadores de pasos como algo netamente visual al momento de maquetar nuestro componente y evitamos que haya información redundante para quienes usen lectores de pantalla.

  • Si lo notas, en el primer li tenemos el atributo aria-current="step". Este atributo es un estado de aria que indica a los lectores de pantalla sobre cuál es el elemento en el que estamos. En NVDA (un lector de pantalla gratuito de Windows) se leería de la siguiente manera:

    Pasos del análisis, lista con 5 elementos 1, paso actual, Activos

    Si quieres conocer más sobre aria-current, puedes ver la documentación de la MDN al respecto⁠ (Abre en una nueva pestaña) . El estado de este atributo cambiará a medida que el usuario avance en el proceso, para lo cual necesitaremos JavaScript, así que tengamos eso en mente cuando lleguemos a esa parte, mientras tanto, miremos el siguiente componente.


Navegación entre secciones (Link permanente)

Una de las secciones del analizador. Abajo están los botones para navegar entre estas

Para evitar sobrecargar al usuario con lo que hay que diligenciar, y para dividir esto cómodamente en partes, decidí dividir esto en secciones. En su momento no fue un mal acercamiento, y de cierto modo iba en la dirección correcta, pero es necesario pulirlo, así que empecemos viendo cómo lo hice anteriormente.

Markup actual Link permanente

<main>
  <section class="page-section | section-on-screen | flow" id="instructions">
    <!-- Contenido de la sección -->
  </section>
  <div
    class="button-container | buttons-on-screen wrapper | flow"
    id="instructionsButton"
  >
    <button class="button" type="button">Comenzar</button>
  </div>
  <section class="page-section | flow" id="assets" hidden>
    <!-- Contenido de la sección -->
  </section>
  <div class="button-container | wrapper | flow" id="assetsButton" hidden>
    <button class="button | back" type="button">Anterior</button>
    <button class="button | next" type="button">Siguiente</button>
  </div>
  <!-- Demás secciones -->
</main>

Mi primer error con esto es que por cada sección estaba manejando un contenedor de botones distinto, esto fue… Innecesario (aunque no estaba seguro de cómo hacer que el navegador entre secciones fuera más dinámico en ese entonces 😅 ) pero ese no es el mayor problema de este markup.

Si bien es en teoría correcto usar un tag de section para dividir cada parte de la página, este markup aporta cero utilidad si dichas secciones no tienen un nombre que los identifique. Sin esta, sería básicamente lo mismo que usar un div para un lector de pantalla (tal como es mencionado en este artículo de Scott O’Hara⁠ (Abre en una nueva pestaña) ), lo que no da el contexto suficiente.

Adicionalmente, al momento de cambiar de sección con los botones de navegación, el usuario puede que no sepa que está pasando al momento de hacer clic, por lo que sería buena idea anunciarles qué ocurre en ese paso.

Por último, la navegación con teclado tiene que tener en cuenta el hecho de que si no hay un elemento seleccionable con teclado dentro de esta sección, deberíamos considerar hacer el propio contenedor de la sección seleccionable con teclado. Esto con el fin de que este tipo de usuarios no tengan problemas al navegar en este sitio.

Bien ¡Hora de implementar estas consideraciones!

¿Cómo arreglar este componente? Link permanente

El componente no está mal per sé, pero si necesita cierta reestructuración en

<main>
  <div class="section-navigator">
    <section aria-labelledby="assetsTitle">
      <h2 id="assetsTitle">Parte 1: Activos</h2>
      <!-- Contenido -->
    </section>
    <section aria-labelledby="liabilitiesTitle" hidden>
      <h2 id="liabilitiesTitle">Parte 2: Pasivos y patrimonio</h2>
      <!-- Contenido -->
    </section>
    <section aria-labelledby="incomeTitle" hidden>
      <h2 id="incomeTitle">Parte 3: Ingresos, costos y gastos</h2>
      <!-- Contenido -->
    </section>
    <section aria-labelledby="summaryTitle" hidden>
      <h2 id="summaryTitle">Resumen</h2>
      <!-- Contenido -->
    </section>
    <section aria-labelledby="reportTitle" hidden>
      <h2 id="reportTitle">Reporte</h2>
      <!-- Contenido -->
    </section>
  </div>
  <div class="button-navigation">
    <button type="button">Paso anterior</button>
    <button type="button">Paso siguiente</button>
  </div>
  <div
    id="sliderLiveRegion"
    class="sr-only"
    aria-live="polite"
    aria-atomic="true"
  >
    Parte 1 de 5
  </div>
</main>

Ok, esto parece ser bastante información, así que desglosémoslo:

  • Agregar todo esto dentro del elemento main es buena idea, ya que main indica al usuario cuál es el contenido principal del sitio.
  • Los elementos section reciben un nombre usando los h2 dentro de este usando el atributo aria-labelledby conectando con el id del h2. Como mencioné anteriormente, una etiqueta de section sin un nombre es para un lector de pantalla básicamente lo mismo que un div. Usamos el h2 como etiqueta de este section para evitar redundancias.
  • Para anunciarle al usuario en qué sección va al momento de cambiar entre estas, tendremos que crear lo que se conoce como un live region, que es un elemento dentro del DOM que anunciará los cambios internos a un lector de pantalla. Para hacer esto, usaremos un div que va a tener los atributos aria-live y aria-atomic.
  • aria-atomic se usa para que en el caso de que haya un cambio de contenido en este elemento, anuncie solamente los cambios que hay (en caso de que sea false, que es el valor por default) o si anuncie todo el contenido (si es true) En nuestro caso, necesitaremos que anuncie todo el cambio, por lo que usaremos true. Dicho cambio de contenido se hará con JavaScript, por lo que esto se verá en la tercer parte de esta documentación.
  • Por otro lado aria-live declara cómo será anunciado el cambio. Este atributo tiene 3 valores:
    • off : valor por default, indica que no se anunciará su cambio a no ser que el usuario esté enfocado en esa región.
    • polite: anuncia el cambio sin interrumpir cualquier otro diálogo de un lector de pantalla.
    • assertive: anuncia el cambio con una alta prioridad e interrumpirá cualquier otro diálogo que un lector de pantalla esté haciendo en el momento. Esta opción puede ser invasiva así que hay que usarse con precaución. Para este analizador, la mejor opción es polite para que anuncie el cambio sin interrumpir ningún otro tipo de acción.
  • Por último, mencioné que en caso de que las secciones no tengan contenido que pueda ser seleccionado por teclado, hay que asegurarnos que este mismo contenedor lo sea. Esto se puede lograr usando el atributo tabindex="0" en el elemento de section pero he decidido omitir esta parte. Dentro de todas las secciones habrá al menos un elemento que se pueda seleccionar con teclado por lo que podremos omitir agregar este atributo.
  • Wow, a pesar de que no hubo tantos cambios a nivel estructural el componente ha cambiado bastante. Revisemos otro componente y mejorémoslo.


    Input con ventana de ayuda (Link permanente)

    Sección de input de datos y modal para describir el campo

    Este analizador recibirá información financiera del usuario para realizar cálculos. En caso de que este no sepa qué agregar aquí, se podrá ver abrir un modal describiendo el campo. El markup de estos componentes no está tan mal, ¿verdad?

    Markup actual Link permanente

    <!-- Input -->
    <label for="cash"
      >Efectivo
      <button class="button" data-size="small" type="button" data-name="Efectivo">
        ?
      </button>
    </label>
    <input
      class="current-assets-value"
      id="cash"
      type="number"
      name="cash"
      data-value="result"
      placeholder="0"
    />
    
    <!-- Modal -->
    <section class="modal" id="helpModal" hidden>
      <article>
        <div class="modal-header">
          <h3><!-- Título del modal agregado con JS --></h3>
          <span class="close" aria-label="Cerrar" tabindex="0">&times;</span>
        </div>
        <div class="modal-content | flow">
          <p><!-- Contenido agregado con JS --></p>
        </div>
      </article>
    </section>
    

    ¿Qué estaba pensando cuando maqueté esto? Ok, vamos por partes:

    • A pesar de que la idea de usar un label y un input conectados por el atributo for, cometí un error considerable al incluir el botón de ayuda dentro del label ¿Por qué es una mala decisión? Hice una prueba con NVDA y así es como se lee este componente:

      Efectivo ? botón Giratorio se puede editar 0 vacío

      Ya explicaré qué es esto de “botón giratorio” luego, lo importante aquí es que esta lectura realmente no tiene nada de sentido. Lee la etiqueta del input e inmediatamente lee el texto del botón pero sin considerarlo como un botón (y no, el “botón giratorio” no tiene nada que ver con el botón dentro del label)

    • Hablemos del button en sí. Solo tiene un signo de interrogación (el cual puedes ver en la transcripción que hice de la lectura que hizo NVDA) dentro lo cual hace que se lee raro y no da el contexto suficiente a los lectores de pantalla sobre lo que este hace.

    • Esto no es tanto de accesibilidad si no de usabilidad: un modal tal vez no sea el mejor modo de presentar la información al usuario. Son descripciones relativamente cortas y tal vez un modal sea muy invasivo. Sin embargo un modal si tiene espacio en este analizador, así que dejemos esto de lado por ahora.

    • Dicho esto ¿Cómo exponemos la información de ayuda al usuario? Se me ocurrió que la mejor opción para mostrar la información es a través de un toggletip, un botón que al momento de hacerle clic muestre la información en forma de una ventana flotante. Dejará de mostrarla al hacer clic a otro lado en pantalla, por lo que mostrar u ocultar la información de ayuda será más fácil.

    Ya una vez cubierto los problemas de este componente… ¡Vamos a arreglarlo!

    ¿Cómo arreglar este componente? Link permanente

    <div class="input-and-toggletip">
      <label for="cash">
        <span>Efectivo</span>
        <input id="cash" type="number" name="cash" placeholder="0" />
      </label>
      <div class="toggletip-container">
        <button>
          <span class="sr-only">Más información sobre efectivo</span>
          <span aria-hidden="true">?</span>
        </button>
        <span role="status">
          <!-- Descripción del input agregada con JS -->
        </span>
      </div>
    </div>
    

    Vaya, esta parece una reducción del markup a comparación de como estaba antes, pero igual tiene varios detalles a destacar:

    • Para empezar, saqué el botón del label, así que evitaremos cualquier confusión a lectores de pantalla. Decidí usar un acercamiento distinto de poner el input dentro del label solamente para usarlo como contenedor al momento de organizar el layout con CSS. Esto no afecta en nada la semántica siempre que uses el atributo for del label para relacionarlo con el id del input.

    • La clase sr-only está para ocultar visualmente este elemento con el fin de describir el botón, mientras que el signo de interrogación está en un span con el atributo aria-hidden="true" para esconderlo de los lectores de pantalla. Con esto nos aseguramos que el botón tenga una descripción en los lectores de pantalla sin que el signo de interrogación interfiera.

    • Para mostrar el contenido a los lectores de pantalla, usaremos un span con el atributo role="status" esto creará un live region como el que usamos en la sección anterior. Excepto que en lugar de ocultarlo visualmente, este también se verá al momento de agregar el contenido.

      El role="status" se usa para dar información a forma de aviso al usuario, que es justo lo que necesitamos para nuestro toggletip, y al momento de inyectarle los nodos con la información dentro de este contenedor usando JavaScript, será anunciado en un lector de pantalla.


    Sumatoria de grupo (Link permanente)

    Lista del grupo de activo corriente. El último campo suma lo que se ingrese en los cuatro campos anteriores

    El propósito de este componente es mostrar la sumatoria del grupo. Este también funcionará a modo de subtotal y el resultado de estos va a alimentar otro componente de resultado. Este es un componente bastante simple y usé en su momento lo que creía mejor, pero realmente no es semánticamente correcto. Veamos como modificar este componente:

    Markup actual Link permanente

    <label for="current-assets-total"><strong>Total activo coriente</strong></label>
    <input
      class="current-assets-value"
      id="currentAssetsTotal"
      type="number"
      name="current-assets-total"
      data-value="result"
      data-group-total="true"
      value="0"
      disabled
    />
    

    Este componente solo tiene un problema: no se expone como resultado ante lectores de pantalla, será expuesto como un campo de número que está deshabilitado. Esto no es ideal ¿Qué semántica usar entonces? ¿Hay si quiera un elemento que sea adecuado para este problema? Curiosamente, ¡si lo hay! Es hora de excavar entre esos elementos oscuros del HTML para resolver este problema.

    ¿Cómo arreglar este componente? Link permanente

    <!-- Opción 1 -->
    <label for="current-assets">
      <span>Total activos corrientes</span>
      <output
        id="current-assets"
        for="cash investments accounts-receivable inventory"
        name="current-assets"
        >0</output
      >
    </label>
    
    <!-- Opción 2 -->
    <p id="current-assets-title">Total activos corrientes</p>
    <output id="current-assets" for="cash investments accounts-receivable inventory"
      >0</output
    >
    

    El elemento output está específicamente diseñado para inyectarle el resultado de un cálculo, ¡y esto es justo lo que este proyecto necesita! Veamos un poco su sintaxis.

    • output es un elemento que puede nombrarse un elemento label por lo que la primer sintaxis es más lógica; sin embargo, he sido informado de que este tiene problemas para ser leído en TalkBack (el lector de pantallas de Android) e incluso por ciertos lectores de pantalla poco conocidos como Orca⁠ (Abre en una nueva pestaña) , por lo que dejo como opción la segunda sintaxis usando aria-labelledby para ser etiquetado por el elemento p que le precede.

    • Si notan este ejemplo, el elemento output tiene un atributo llamado for que contiene varios elementos separados por un espacio. Esto relacionará este elemento con todos los inputs que estén implicados en su resultado. Así cuando estemos escribiendo un valor en estos inputs, y después de esto el resultado sea inyectado con JavaScript en el este elemento, anunciará su resultado.

    • Este elemento es un live-region, tal como el que usamos en el componente de navegación entre secciones. Esto hará que al momento de agregarse el resultado con JavaScript, será anunciado inmediatamente. Esta será la lectura que reciba el lector de pantalla.

      "0. Total activos corrientes"

      Por defecto, el comportamiento de esta live region será polite, es decir que se anunciará después de lo que el lector de pantalla tenga que anunciar. Esto es útil porque si has sido observador, cada pantalla puede tener hasta 3 output así que se anunciará cada cambio 1 a la vez.


    Tablas de resumen (Link permanente)

    Tabla que muestra el resumen de la información ingresada a lo largo del proceso

    El propósito de este componente es generar un resumen de la información ingresada en el formulario. En retrospectiva escogí un buen modo de mostrar la información usando el elemento table, pero revisando los detalles de esta… Hay bastante que mejorar

    Markup actual Link permanente

    <table>
      <!-- Encabezado de tabla -->
      <tr>
        <th class="top-radius" colspan="2">Activo</th>
      </tr>
      <!-- Cuerpo de la tabla -->
      <tr>
        <td>Efectivo</td>
        <td>$1.00</td>
      </tr>
      <!-- Demás rubros de la tabla -->
      <tr>
        <td><b>Total activo corriente</b></td>
        <td>$4.00</td>
      </tr>
      <tr>
        <td class="bottom-left-radius"><b>Total activo:</b></td>
        <td class="bold bottom-right-radius" id="assetsTotalSummary">
          <!-- Total de la tabla agregado con JS -->
        </td>
      </tr>
    </table>
    

    Como dije antes, usar table fue un buen acercamiento, pero no es suficiente para hacer una tabla accesible, veamos qué cambios necesita.

    • Esta tabla está dividida en tres partes: un encabezado donde muestra el grupo que se resume, el cuerpo de la tabla con los rubros y una parte final donde está la suma total de la tabla. Sin embargo esas partes no están presentes en el markup y esto generaría confusión a quienes usen un lector de pantalla para navegar.
    • Podemos dividir esto aun más inclusive. Esta tabla tiene dos secciones: una para activo corriente, y una para activo no corriente, cada una con su respectivo subtotal, por lo cual sería buena idea separar cada sección de la tabla.
    • No hay nada que indique si hay algún encabezado en la tabla. Tenemos encabezados tanto de filas como de columnas pero aquí todo sería leído igual por un lector de pantallas:

    ¿Cómo arreglar este componente? Link permanente

    <table>
      <caption>
        Resumen de activos
      </caption>
      <!-- Encabezado -->
      <thead>
        <tr>
          <th scope="col">Cuenta</th>
          <th scope="col">Valor</th>
        </tr>
      </thead>
      <!-- Sección 1 -->
      <tbody>
        <tr>
          <th colspan="2" scope="colgroup">Activo corriente</th>
        </tr>
        <tr>
          <th scope="row">Efectivo</th>
          <td>$1.00</td>
        </tr>
        <!-- Demás cuentas del grupo -->
        <tr>
          <th scope="row">Total activo corriente</th>
          <td>$4.00</td>
        </tr>
      </tbody>
      <!-- Sección 2 -->
      <tbody>
        <tr>
          <th scope="colgroup">Activo no corriente</th>
        </tr>
        <tr>
          <th scope="row">Propiedad, planta y equipo</th>
          <td>$1.00</td>
        </tr>
        <!-- Demás cuentas del grupo -->
        <tr>
          <th scope="row">Total activo no corriente</th>
          <td>$3.00</td>
        </tr>
      </tbody>
      <!-- Total de la tabla -->
      <tfoot>
        <tr>
          <th scope="row">Total activo</th>
          <td>$7.00</td>
        </tr>
      </tfoot>
    </table>
    

    Vaya que ha cambiado este componente, revisemos los cambios hechos

    • Decidí hacer un título más específico y en lugar de agregarlo en una fila de la tabla, usé la etiqueta caption, lo cual es la semántica correcta para este caso.

    • Las secciones de la tabla fueron debidamente diferenciadas usando las etiquetas thead para el encabezado, tbody para el cuerpo y tfoot para la sumatoria de toda la tabla. Como puedes ver, esta tabla tiene dos tbody, una para cada sección (activos corrientes y activos no corrientes). Esto es semánticamente correcto siempre y cuando los tbody sean consecutivos (el cual es nuestro caso).

    • Todos los encabezados de fila y de columna ahora usan la etiqueta th lo que ayuda a dar el contexto a los lectores de pantalla sobre qué es un encabezado en esta tabla. Sin embargo, esto no es suficiente, y por eso esta etiqueta tiene el atributo scope que ayuda a diferenciar qué tipo de encabezado es: col indicará que es un encabezado de columna mientras que row indicará que es un encabezado de fila.

    • Los encabezados de sección se hicieron usando la misma lógica del punto anterior, pero como estos ocupan las dos columnas (lo cual se logra usando el atributo colspan="2") también queremos indicar que ese encabezado es de un grupo de columnas, para lo cual usamos el atributo scope="colgroup".


    Modal (Link permanente)

    Como pudimos ver en la sección de input con ventana de ayuda el modal se usa para darle explicar a los usuarios qué compone el rubro, pero esto ahora se hará a través de un toggletip ¿Para qué usar el modal entonces? Bien, este ahora servirá como ventana para indicar posibles errores antes de generar el resumen. Así que traigamos de nuevo la semántica usada y veamos lo que hay que corregir:

    Markup actual Link permanente

    <section class="modal" id="helpModal" hidden>
      <article>
        <div class="modal-header">
          <h3><!-- Título del modal agregado con JS --></h3>
          <span class="close" aria-label="Cerrar" tabindex="0">&times;</span>
        </div>
        <div class="modal-content | flow">
          <p><!-- Contenido agregado con JS --></p>
        </div>
      </article>
    </section>
    

    En serio ¿Qué estaba pensando al hacer este componente? La idea de usar un section para este ya es errada de por sí, pero usar article en serio no fue buena idea. Veamos un poco qué es lo que hacen cada uno.

    • section es un elemento de landmark genérico, básicamente se usa cuando necesitas diferenciar una región en un lector de pantalla pero no entra en las categorías cubiertas por otros elementos (header, nav, footer, main, etc) Sin embargo, como he mencionado anteriormente, un section no tendrá ningún significado a no ser que le agregues una etiqueta así que como está ahora, este tiene el mismo nivel semántico de un div.
    • article es otro elemento de landmark cuyo propósito es mostrar un contenido que puede funcionar de forma independiente del sitio. Piensa en este elemento de esta manera: ¿Este contenido podría ser mostrado en otro sitio sin perder significado? Por ejemplo, un entrada de blog tuya, un post de un foro, un comentario de una red social, etc. En el caso de este modal, este contenido perdería significado fuera de este analizador financiero, por lo que no es la etiqueta adecuada.
    • Hablemos del botón de modal… Que no es un botón, es un span y por alguna razón le agregué el tabindex="0" para “hacerlo accesible por teclado”. Verán, un button tiene una característica importante además de su semántica y de que puedes seleccionarlo con teclado: puede ser activado por teclado usando las teclas Enter y Space, cosa que con el span no puedes.

    ¿Cómo arreglar este componente? Link permanente

    <div role="dialog" aria-labelledby="modalTitle" tabindex="-1">
      <h3 id="modalTitle">Inconsistencias en la información</h3>
      <ol role="list">
        <li>
          <!-- Inconsistencia del modal agregada con JS -->
        </li>
        <!-- Demás inconsistencias del modal -->
      </ol>
      <button>
        <span class="sr-only">Cerrar modal</span>
        <span aria-hidden="true">&times;</span>
      </button>
    </div>
    

    Por suerte no es una estructura particularmente difícil de cambiar a nivel general. El contenido del modal cambia, por lo que su estructura interna cambia. No me enfocaré en esta parte, si no en los cambios de semántica resaltados anteriormente (el contenedor y el botón)

    • En lugar de usar un section, podemos usar un div con el atributo role="dialog". Dicho rol se usa para ventanas que muestran contenido aparte del resto del sitio. Para que sea debidamente reconocido, es necesario que dicho elemento tenga un nombre, cosa que cubrimos usando el atributo aria-labelledby para relacionarlo con el título de este usando el id.
    • Dicho elemento tiene el atributo tabindex="-1" lo que hace que no pueda ser seleccionado por teclado, pero si pueda ser enfocado usando JavaScript. Esto es necesario a futuro para añadir su funcionalidad y que sigua siendo accesible.
    • El botón va a estar al final del elemento para que sea enfocado de último. Si este modal se abre, es necesario que el usuario primero navegue por todos los errores antes de cerrar el modal, por lo que es necesario que esté de último en el HTML. Luego podremos ubicarlo con CSS en la parte superior del modal.
    • El elemento button tiene un elemento con la clase sr-only para ocultarlo de forma visual, a su vez que sirve como nombre de este en un lector de pantalla. El elemento &times; es para dibujar el ícono necesario, pero será ocultado de un lector de pantalla usando el atributo aria-hidden="true".

    Otra alternativa de semántica Link permanente

    Usar un contenedor con el atributo de role="dialog" da la semántica adecuada, pero tendremos que hacer bastantes cosas con CSS y JavaScript para hacerlo funcional y accesible: cerrar el modal con la tecla Esc, cerrar el modal al hacer clic por fuera, asegurarte de que el modal siempre está posicionado por encima de los demás elementos.

    Sin embargo, se han hecho esfuerzos para crear una etiqueta de HTML que cumpla estas funciones, y ahora tenemos una nueva etiqueta para este tipo de casos: dialog. Es bastante completo, tiene sus propios métodos de JavaScript para abrir y cerrar y también tiene sus propias reglas de CSS para crear el overlay.

    El único problema de esto es que el elemento dialog aun no tiene soporte total en navegadores. El último de los 4 navegadores principales en implementarlo fue Firefox y lo hizo en febrero del 2022, tal como puedes ver en la página de caniuse.com sobre este elemento⁠ (Abre en una nueva pestaña) por lo que si tienes que darle soporte a versiones anteriores, no recomiendo usarlo. Si quieres conocer más sobre este componente, puedes leer más en este artículo de Chris Coyier sobre el elemento dialog⁠ (Abre en una nueva pestaña) .


    Para terminar (Link permanente)

    Al momento de crear un proyecto, es importante pensar en la estructura de HTML de estos. HTML es la capa más robusta y resiliente de nuestro sitio y una buena semántica nos ayuda bastante a hacer un sitio accesible para distintos grupos de personas, como quienes usan un lector de pantalla o quienes necesitan navegar por teclado.

    Este proceso de rediseñar este sitio está lejos de acabar, y probablemente necesite una segunda parte para cubrir otros elementos de HTML de este proyecto, como el uso de landmarks adecuado. Además, en caso de que se me ocurra cualquier mejora en la semántica de estos componentes, estaré actualizándolo. Si quieres saber cómo continúa este proyecto recuerda seguirme en Bluesky⁠ (Abre en una nueva pestaña) donde publicaré cualquier actualización al respecto.

    ¡Hasta la próxima vez!

    Eda de la serie The Owl House saliendo de su casa mientras dice de forma muy alegre 'byeeee'