display: contents y accesibilidad

Publicado el:

display: contents es una gran herramienta que nos puede ayuda a crear layouts, sin embargo, hay consideraciones de accesibilidad a tener en cuenta. Este artículo te explica lo que debes saber sobre esta propiedad.


Now you can find this article in English on Tealfeed⁠ (Abre en una nueva pestaña) !

Introducción (Link permanente)

Entre tantas opciones que tenemos hoy en día para crear layouts, hay una que considero que no se está usando lo suficiente a pesar de tener bastante potencial, y esta es display: contents. Bien usada, es una herramienta que nos añade flexibilidad en cuanto a lo que es posible hacer, pero gracias a ciertos temas de implementación entre navegadores, puede generar fallos de accesibilidad en ciertos puntos.

Este artículo busca explicar que usos puedes darle a esta propiedad de CSS y cómo usarla de forma adecuada sin perjudicar la accesibilidad.


¿Qué es display: contents? (Link permanente)

display: contents es una propiedad que permite borrar la caja contenedora y hace que los hijos de este se comporten como si fueran sus elementos adyacentes. Para ponerlo mejor, veamos un ejemplo. Supongamos que tengo este markup

<header>
  <a href="#">ir a inicio</a>
  <nav>
    <ul>
      <li>
        <a href="#">Ropa</a>
      </li>
      <li>
        <a href="#">Accessorios</a>
      </li>
      <li>
        <a href="#">Zapatos</a>
      </li>
    </ul>
  </nav>
</header>

Si aplicamos esta propiedad al ul, para fines de layout es como si este no existiera, lo que hará que los li se comporten como si fueran directos de nav. La magia de esto reside en que todo esto pasa sin alterar la semántica del markup, lo que nos da mucha más flexibilidad al momento de crear el layout que necesitamos.

Dicho esto ¿Qué usos prácticos puede tener esta propiedad? Esto lo veremos a continuación.


Ejemplo (Link permanente)

Para este caso, voy a usar un ejemplo que este mismo sitio usa. Si revisas la sección de artículos de mi sitio vas a notar una lista de artículos y una parte que dice “Próximamente” para mostrar qué artículo pienso escribir (por favor, no hablemos de la frecuencia con la que escribo en mi blog 😅) pero hay un detalle: a pesar de que esta sección de “Próximamente” es parte de los blogs, no es parte del listado como tal. Al momento de maquetar esto yo lo tomé como información complementaria, lo que dio como resultado este markup.

<section class="blog-entries" aria-labelledby="blog-title" id="blog">
  <div class="wrapper">
    <h2 id="blog-title">Blogs recientes</h2>
    <div class="blog-entries-container">
      <ul role="list">
        <li>
          <article class="blog-entries__card">
            <!-- Markup del artículo -->
          </article>
        </li>
        <li>
          <article class="blog-entries__card">
            <!-- Markup del artículo -->
          </article>
        </li>
        <li>
          <article class="blog-entries__card">
            <!-- Markup del artículo -->
          </article>
        </li>
        <li>
          <article class="blog-entries__card">
            <!-- Markup del artículo -->
          </article>
        </li>
      </ul>
      <aside class="blog-entries__next">
        <h3>Próximamente:</h3>
        <p><code>display: contents</code> y accesibilidad</p>
      </aside>
    </div>
  </div>
</section>

Pude haberlo dejado el aside como parte de la lista, pero de nuevo, para mi, este no debía hacer parte del listado de artículos publicados, por lo que decidí este acercamiento, pero luego viene el problema de que en temas de layout, yo quería de que dicho aside apareciera después del primero, cosa que no puedo hacer por la caja contenedora del ul. Ahí es donde display: contents viene al rescate. Empecemos a aplicar estas reglas en la hoja de estilos.

.blog-entries-container {
  display: grid;
}

.blog-entries-container ul {
  display: contents;
}

.blog-entries-container ul li:first-child {
  order: -2;
}

.blog-entries__next {
  order: -1;
}

Ya con esto, las entradas de blog están en el orden que quiero que estén y la semántica del HTML tiene sentido para los lectores de pantalla (o al menos, en la interpretación que le decidí dar en el diseño). Cosas como estas muestran que display: contents tiene una utilidad interesante. Sin embargo, hay que tener en cuenta unos cuantos riesgos de accesibilidad al momento de usarlos. A continuación vamos a hablar de esos problemas:


Riesgos de accesibilidad (Link permanente)

Accesibilidad por teclado Link permanente

Este es probablemente la situación menos común que te vayas a encontrar, pero es importante tenerla en cuenta: cuando usas display: contents en un elemento que se puede seleccionar por teclado (como button, a o input) este dejará de ser seleccionable por teclado.

Eso es muy problemático porque como probablemente sabrás, estos elementos se pueden navegar usando la tecla Tab por una razón: las personas con discapacidades motrices necesitan poder seleccionar estos elementos sin necesidad de un mouse, y el navegador les agrega esa funcionalidad precisamente por eso mismo. Sin embargo, esto es algo que display: contents elimina, por lo cual debe evitar usarse a todas costa en elementos interactivos.

Accesibilidad de lectores de pantalla Link permanente

A diferencia de las preocupaciones de accesibilidad por teclado, la accesibilidad de lector de pantalla si se puede ver afectada con mucha más facilidad.

Verán, cuando se empezó a implementarse en los navegadores, pareciera que las especificaciones no fueran del todo claras o tal vez fue una omisión colectiva, pero en su principio display: contents removía la semántica de los elementos, y esto es algo que tardó un poco en arreglarse. El navegador que más tardó en arreglar este bug fue Safari en la versión 15.6 que fue lanzada en julio de 2022. Esto es muy poco tiempo, así que es muy probable que si lo implementes ahora, vas a tener bugs de accesibilidad en Safari.

Con esto en mente, es válido preguntarse si está bien o no usar esta propiedad en tus sitios. Mi respuesta a esto es que depende. Hazte la siguiente pregunta ¿Qué pasa si semánticamente no se reconoce el contenedor que estoy eliminando con display: contents.

Volviendo a mi contenedor de blogs, lo peor que pasaría es que deja de reconocerse como una lista de artículos y en su lugar van a ser solo 4 artículos sin listar. Ese es un modo de mostrarlo aceptable que no compromete particularmente la experiencia de usuarios de lectores de pantalla, por lo que está bien usarlo en este caso, pero veamos un caso donde esto comprometería bastante la accesibilidad del sitio. Veamos esta barra de navegación

Pantallazo de una barra de navegación con seis links. El fondo es verde, los tres primeros links están a la izquierda, en medio de la barra está el logo (en este caso, una estrella) y después del logo, los otros tres links.

Para hacer esta barra de navegación primero iniciemos por el markup:

<header>
  <svg>
    <!-- Logo -->
  </svg>
  <nav>
    <ul>
      <li><a href="#">Inicio</a></li>
      <li><a href="#">Asia</a></li>
      <li><a href="#">África</a></li>
      <li><a href="#">América</a></li>
      <li><a href="#">Europa</a></li>
      <li><a href="#">Oceania</a></li>
    </ul>
  </nav>
</header>

Pero tenemos el problema de que tanto el elemento nav como el elemento ul están interfiriendo para que podamos acomodar los li al mismo nivel que el logo. Para eso usamos display: contents en estos contenedores:

nav,
ul {
  display: contents;
}

Pero es aquí donde tenemos el problema. Recuerda: en Safari aun hay que lidiar con el hecho de que esto borrará la semántica del elemento, por lo que quedará como un SVG y seis links, saltándose por completo la lista, y más importante aun, la semántica de la barra de navegación. Son estos casos que aun toca revisar con cuidado, no puedes asumir que alguien va a tener la última versión del navegador y si esto interfiere tanto con la navegación de lector de pantalla, hay que tomar decisiones, que puede ser alguna de estas:

  • Discutir con el área de diseño para ajustar el contenido, teniendo en mente estas preocupaciones de accesibilidad.
  • Buscar otro markup que permita respetar la semántica y replicar este diseño.
  • Hacer pruebas con lectores de pantalla en versiones antiguas para saber si la experiencia de usuario si se ve gravemente comprometida.
  • Validar con usuarios de lectores de pantalla para saber su opinión de este componente (siendo justos, esto es algo que siempre debería hacerse, pero no siempre está en nuestras posibilidades).

La buena noticia es que esto está solucionado en la versión 16 de Safari, así que esas consideraciones van a verse cada vez menos. La mala noticia es que no es el único problema de display: contents. En todos los navegadores, usar esta propiedad borrará la semántica del elemento button (que igual no debería usarse por lo mencionado sobre accesibilidad de teclado) y solamente en Safari borrará la semántica del elemento table (aun en la versión 16) por lo que es recomendable no usarla en estos elementos.


Para terminar (Link permanente)

display: contents tiene bastante potencial, y bien usado puede ayudar muchos problemas de diseño mientras se mantiene la semántica necesaria para un sitio; sin embargo, ten cuidado porque su implementación en navegadores no es perfecta y puede crear problemas de accesibilidad. En un futuro esto será cosa del pasado, pero es importante tener estas consideraciones en mente.

Sin más que decir, recuerden que pueden seguirme en mi cuenta de Twitter⁠ (Abre en una nueva pestaña) . ¡Hasta la próxima vez!

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