Compila un componente de selección múltiple

Una descripción general fundamental de cómo crear un componente de selección múltiple responsivo, adaptable y accesible para ordenar y filtrar experiencias del usuario.

En esta publicación, quiero compartir mi forma de pensar sobre cómo crear un componente de selección múltiple. Prueba la demostración.

Demo

Si prefieres ver un video, aquí tienes una versión de esta publicación en YouTube:

Descripción general

A menudo, a los usuarios se les presentan elementos, a veces muchos, y en estos casos puede ser una buena idea proporcionar una forma de reducir la lista para evitar la sobrecarga de opciones. En esta entrada de blog, se explora la IU de filtrado como una forma de reducir las opciones. Para ello, presenta atributos de elementos que los usuarios pueden seleccionar o anular, lo que reduce los resultados y, por lo tanto, la sobrecarga de opciones.

Interacciones

El objetivo es permitir la navegación rápida por las opciones de filtro para todos los usuarios y sus diferentes tipos de entrada. Esto se entregará con un par de componentes adaptables y responsivos. Una barra lateral tradicional de casillas de verificación para computadoras de escritorio, lectores de pantalla y teclados, y un <select multiple> para usuarios táctiles.

Captura de pantalla de comparación que muestra el modo claro y oscuro para computadoras de escritorio con una barra lateral de casillas de verificación en comparación con iOS y Android para dispositivos móviles con un elemento de selección múltiple.

Esta decisión de usar la selección múltiple integrada para dispositivos táctiles y no para computadoras de escritorio ahorra trabajo y crea trabajo, pero creo que ofrece experiencias adecuadas con menos deuda de código que compilar toda la experiencia responsiva en un componente.

Pantalla táctil

El componente táctil ahorra espacio y ayuda a la precisión de la interacción del usuario en dispositivos móviles. Ahorra espacio porque contrae una barra lateral completa de casillas de verificación en una experiencia táctil de superposición integrada de <select>. Ayuda a la precisión de la entrada, ya que muestra una gran experiencia de superposición táctil que proporciona el sistema.

Vista previa de una captura de pantalla del elemento de selección múltiple en Chrome para Android, iPhone y iPad. El iPad y el iPhone tienen la opción de selección múltiple activada, y cada uno obtiene una experiencia única optimizada para el tamaño de la pantalla.

Teclado y control de juegos

A continuación, se muestra cómo usar un <select multiple> desde el teclado.

Esta selección múltiple integrada no se puede aplicar diseño y solo se ofrece en un diseño compacto que no es adecuado para presentar muchas opciones. ¿Ves cómo no puedes ver la amplitud de opciones en ese pequeño cuadro? Si bien puedes cambiar su tamaño, aún no es tan fácil de usar como una barra lateral de casillas de verificación.

Marca

Ambos componentes se incluirán en el mismo elemento <form>. Los resultados de este formulario, ya sean casillas de verificación o una selección múltiple, se observarán y usarán para filtrar la cuadrícula, pero también se pueden enviar a un servidor.

<form>

</form>

Componente de casillas de verificación

Los grupos de casillas de verificación deben unirse en un elemento <fieldset> y se les debe asignar un <legend>. Cuando el código HTML se estructura de esta manera, los lectores de pantalla y FormData comprenderán automáticamente la relación de los elementos.

<form>
  <fieldset>
    <legend>New</legend>
    … checkboxes …
  </fieldset>
</form>

Con la agrupación establecida, agrega un <label> y un <input type="checkbox"> para cada uno de los filtros. Elegí unir el mío en un <div> para que la propiedad gap de CSS pueda espaciarlos de manera uniforme y mantener la alineación cuando las etiquetas se convierten en varias líneas.

<form>
  <fieldset>
    <legend>New</legend>
    <div>
      <input type="checkbox" id="last 30 days" name="new" value="last 30 days">
      <label for="last 30 days">Last 30 Days</label>
    </div>
    <div>
      <input type="checkbox" id="last 6 months" name="new" value="last 6 months">
      <label for="last 6 months">Last 6 Months</label>
    </div>
   </fieldset>
</form>

Captura de pantalla con una superposición informativa para la leyenda y los elementos del conjunto de campos, que muestra el color y el nombre del elemento.

Componente <select multiple>

Una función poco utilizada del elemento <select> es multiple. Cuando el atributo se usa con un elemento <select>, el usuario puede elegir muchos de la lista. Es como cambiar la interacción de una lista de botones de selección a una lista de casillas de verificación.

<form>
  <select multiple="true" title="Filter results by category">
    …
  </select>
</form>

Para etiquetar y crear grupos dentro de un <select>, usa el elemento <optgroup> y asígnale un atributo y un valor label. Este elemento y valor del atributo son similares a los elementos <fieldset> y <legend>.

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      …
    </optgroup>
  </select>
</form>

Ahora, agrega los elementos <option> para el filtro.

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      <option value="last 30 days">Last 30 Days</option>
      <option value="last 6 months">Last 6 Months</option>
    </optgroup>
  </select>
</form>

Captura de pantalla de la renderización para computadoras de escritorio de un elemento de selección múltiple.

Seguimiento de entradas con contadores para informar a la tecnología de accesibilidad

En esta experiencia del usuario, se usa la técnica de rol de estado para hacer un seguimiento y mantener el recuento de filtros para lectores de pantalla y otras tecnologías de accesibilidad. En el video de YouTube, se muestra la función. La integración comienza con HTML y el atributo role="status".

<div role="status" class="sr-only" id="applied-filters"></div>

Este elemento leerá en voz alta los cambios realizados en el contenido. Podemos actualizar el contenido con contadores de CSS a medida que los usuarios interactúan con las casillas de verificación. Para ello, primero debemos crear un contador con un nombre en un elemento superior de las entradas y el elemento de estado.

aside {
  counter-reset: filters;
}

De forma predeterminada, el recuento será 0, lo cual es excelente, ya que nada es :checked de forma predeterminada en este diseño.

A continuación, para incrementar nuestro contador creado recientemente, segmentaremos los elementos secundarios del elemento <aside> que sean :checked. A medida que el usuario cambia el estado de las entradas, el contador filters se suma.

aside :checked {
  counter-increment: filters;
}

El CSS ahora conoce el recuento general de la IU de la casilla de verificación, y el elemento de rol de estado está vacío y espera valores. Dado que CSS mantiene el recuento en la memoria, la función counter() permite acceder al valor desde el contenido del elemento pseudo:

aside #applied-filters::before {
  content: counter(filters) " filters ";
}

El código HTML del elemento de rol de estado ahora anunciará "2 filtros " a un lector de pantalla. Es un buen comienzo, pero podemos hacer mejor, como compartir el recuento de los resultados que actualizaron los filtros. Haremos este trabajo desde JavaScript, ya que está fuera de lo que pueden hacer los contadores.

Captura de pantalla del lector de pantalla de macOS que anuncia la cantidad de filtros activos.

Nesting excitement

El algoritmo de contadores se sintió muy bien con anidado de CSS-1, ya que pude poner toda la lógica en un bloque. Se siente portátil y centralizado para leer y actualizar.

aside {
  counter-reset: filters;

  & :checked {
    counter-increment: filters;
  }

  & #applied-filters::before {
    content: counter(filters) " filters ";
  }
}

Diseños

En esta sección, se describen los diseños entre los dos componentes. La mayoría de los estilos de diseño son para el componente de la casilla de verificación de computadoras de escritorio.

El formulario

Para optimizar la legibilidad y la capacidad de escaneo para los usuarios, el formulario tiene un ancho máximo de 30 caracteres, lo que establece un ancho de línea óptica para cada etiqueta de filtro. El formulario usa el diseño de cuadrícula y la propiedad gap para espaciar los conjuntos de campos.

form {
  display: grid;
  gap: 2ch;
  max-inline-size: 30ch;
}

El elemento <select>

La lista de etiquetas y las casillas de verificación consumen demasiado espacio en dispositivos móviles. Por lo tanto, el diseño verifica el dispositivo de puntero principal del usuario para cambiar la experiencia de tacto.

@media (pointer: coarse) {
  select[multiple] {
    display: block;
  }
}

Un valor de coarse indica que el usuario no podrá interactuar con la pantalla con una gran precisión con su dispositivo de entrada principal. En un dispositivo móvil, el valor del puntero suele ser coarse, ya que la interacción principal es táctil. En un dispositivo de escritorio, el valor del puntero suele ser fine, ya que es común tener un mouse o algún otro dispositivo de entrada de alta precisión conectado.

Los fieldsets

El diseño y el estilo predeterminados de un <fieldset> con un <legend> son únicos:

Captura de pantalla de los estilos predeterminados de un fieldset y una leyenda.

Por lo general, para espaciar mis elementos secundarios, usaría la propiedad gap, pero la posición única de <legend> dificulta la creación de un conjunto de elementos secundarios espaciados de manera uniforme. En lugar de gap, se usan el selector de elementos adyacentes y margin-block-start.

fieldset {
  padding: 2ch;

  & > div + div {
    margin-block-start: 2ch;
  }
}

Esto evita que se ajuste el espacio de <legend>, ya que se segmenta solo para los elementos secundarios <div>.

Captura de pantalla que muestra el espacio del margen entre las entradas, pero no la leyenda.

La etiqueta del filtro y la casilla de verificación

Como elemento secundario directo de un <fieldset> y dentro del ancho máximo del 30ch del formulario, el texto de la etiqueta puede ajustarse si es demasiado largo. El texto ajustado es excelente, pero la falta de alineación entre el texto y la casilla de verificación no lo es. Flexbox es ideal para esto.

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
Captura de pantalla que muestra cómo la marca de verificación se alinea con la primera línea de texto en una situación de unión de varias líneas.
Juega más en este Codepen

La cuadrícula animada

Isotope realiza la animación del diseño. Un plugin potente y eficiente para ordenar y filtrar de forma interactiva.

JavaScript

Además de ayudar a organizar una cuadrícula interactiva y animada, JavaScript se usa para pulir algunos aspectos.

Normaliza la entrada del usuario

Este diseño tiene un formulario con dos formas diferentes de proporcionar entradas, y no serializan lo mismo. Sin embargo, con un poco de JavaScript, podemos normalizar los datos.

Captura de pantalla de la consola de JavaScript de DevTools, que muestra el objetivo y los resultados de los datos normalizados.

Elegí alinear la estructura de datos del elemento <select> con la estructura de casillas de verificación agrupadas. Para ello, se agrega un objeto de escucha de eventos input al elemento <select>, en cuyo punto se asignan sus selectedOptions.

document.querySelector('select').addEventListener('input', event => {
  // make selectedOptions iterable then reduce a new array object
  let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
    // parent optgroup label and option value are added to the reduce aggregator
    data.push([opt.parentElement.label.toLowerCase(), opt.value])
    return data
  }, [])
})

Ahora puedes enviar el formulario de forma segura o, en el caso de esta demostración, indicarle a Isotope por qué filtrar.

Finaliza el elemento de rol de estado

El elemento solo cuenta y anuncia el recuento de filtros según la interacción con la casilla de verificación, pero creo que sería una buena idea compartir, además, la cantidad de resultados y asegurarnos de que también se cuenten las opciones del elemento <select>.

Elección del elemento <select> que se refleja en counter()

En la sección de normalización de datos, ya se creó un objeto de escucha en la entrada. Al final de esta función, se conocen la cantidad de filtros elegidos y la cantidad de resultados de esos filtros. Los valores se pueden pasar al elemento de rol de estado de esta manera.

let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length

Resultados reflejados en el elemento role="status"

:checked proporciona una forma integrada de pasar la cantidad de filtros elegidos al elemento de rol de estado, pero no tiene visibilidad de la cantidad filtrada de resultados. JavaScript puede detectar la interacción con las casillas de verificación y, después de filtrar la cuadrícula, agregar textContent como lo hizo el elemento <select>.

document
  .querySelector('aside form')
  .addEventListener('input', e => {
    // isotope demo code
    let filterResults = IsotopeGrid.getFilteredItemElements().length
    document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})

En conjunto, este trabajo completa el anuncio "2 filtros que dan 25 resultados".

Captura de pantalla del lector de pantalla de macOS que anuncia los resultados.

Ahora, nuestra excelente experiencia de tecnología de accesibilidad se entregará a todos los usuarios, independientemente de cómo interactúen con ella.

Conclusión

Ahora que sabes cómo lo hice, ¿cómo lo harías tú? 🙂

Diversifiquemos nuestros enfoques y aprendamos todas las formas de compilar en la Web. Crea una demo, twittea los vínculos y los agregaré a la sección de remixes de la comunidad a continuación.

Remixes de la comunidad

Aún no hay nada que ver.