Una descripción general fundamental de cómo compilar un componente de selección múltiple responsivo, adaptable y accesible para ordenar y filtrar las experiencias del usuario.
En esta publicación, quiero compartir las ideas sobre una forma de crear un componente de selección múltiple. Prueba la demostración.
Si prefieres ver un video, aquí tienes una versión de YouTube de esta publicación:
Descripción general
A menudo, a los usuarios se les presentan elementos, que a veces son muchos elementos, por lo que 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 los atributos del elemento que los usuarios pueden seleccionar o anular la selección, lo que reduce los resultados y, por lo tanto, la sobrecarga de opciones.
Interacciones
El objetivo es habilitar el cruce rápido de 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, teclados y lectores de pantalla, y un <select
multiple>
para usuarios táctiles.
Esta decisión de usar la selección múltiple integrada para el modo táctil y no para las computadoras de escritorio ahorra trabajo y crea trabajo, pero creo que ofrece experiencias adecuadas con menos deuda de código que compilar la experiencia responsiva completa en un solo componente.
Tacto
El componente táctil ahorra espacio y ayuda a mejorar la precisión de la interacción del usuario en dispositivos móviles. Para ahorrar espacio, contrae una barra lateral completa de casillas de verificación en una experiencia táctil de superposición integrada de <select>
. Ayuda a la exactitud de la entrada, ya que muestra una gran experiencia de superposición táctil que proporciona el sistema.
Teclado y control de juegos
A continuación, se muestra una demostración de cómo usar un <select multiple>
desde el teclado.
Esta selección múltiple integrada no se puede diseñar y solo se ofrece en un diseño compacto que no es adecuado para presentar muchas opciones. ¿Ves que no puedes ver la amplitud de opciones en esa pequeña caja? Si bien puedes cambiar su tamaño, aún no se puede usar tanto como una barra lateral de casillas de verificación.
Marca
Ambos componentes estará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 se usarán para filtrar la cuadrícula, pero también se podrían 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 recibir una <legend>
.
Cuando HTML se estructura de esta manera, los lectores de pantalla y FormData comprenderán automáticamente la relación entre los elementos.
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
Una vez que esté la agrupación, agrega <label>
y <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 tengan 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>
Componente <select multiple>
Un atributo del elemento <select>
que se usa muy poco es multiple
.
Cuando se usa el atributo con un elemento <select>
, el usuario puede elegir muchos de los elementos de la lista. Es como cambiar la interacción de una lista de radio 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 una <select>
, usa el elemento <optgroup>
y asígnale un atributo y un valor label
. Este valor del elemento y el 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>
Seguimiento de las entradas con contadores para informar a la tecnología de asistencia
La técnica de función de estado se usa en esta experiencia del usuario para realizar un seguimiento y mantener el recuento de filtros para lectores de pantalla y otras tecnologías de accesibilidad. El video de YouTube
demuestra 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 genial, nada es :checked
de forma predeterminada en este diseño.
A continuación, para aumentar el contador recién creado, nos enfocaremos en elementos secundarios del elemento <aside>
que son :checked
. A medida que el usuario cambie el estado de las entradas, el contador filters
contará.
aside :checked {
counter-increment: filters;
}
CSS ahora reconoce el recuento general de la IU de la casilla de verificación y el elemento de la función de estado está vacío y a la espera de valores. Dado que CSS mantiene el recuento en la memoria, la función counter()
permite acceder al valor desde el contenido de los pseudoelementos:
aside #applied-filters::before {
content: counter(filters) " filters ";
}
El código HTML del elemento de función de estado ahora anunciará "2 filtros " a un lector de pantalla. Este es un buen comienzo, pero podemos hacerlo mejor, como compartir el recuento de los resultados que se actualizaron los filtros. Lo haremos desde JavaScript, ya que está fuera de lo que pueden hacer los contadores.
Anidación de la actividad
El algoritmo de los contadores resultaba muy bueno con CSSnesting-1, ya que pude poner toda la lógica en un bloque. Parece portátil y centralizada 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 diseños de diseño son para el componente de la casilla de verificación de escritorio.
El formulario
Con el objetivo de optimizar la legibilidad y la capacidad de lectura para los usuarios, se le otorga al formulario un ancho máximo de 30 caracteres, lo que básicamente establece un ancho de línea óptica para cada etiqueta de filtro. El formulario usa un 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 ocupan demasiado espacio en los dispositivos móviles. Por lo tanto, el diseño verifica el dispositivo apuntador principal del usuario para cambiar la experiencia táctil.
@media (pointer: coarse) {
select[multiple] {
display: block;
}
}
Un valor de coarse
indica que el usuario no podrá interactuar con la pantalla con grandes cantidades de 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 conectado un mouse o algún otro dispositivo de entrada de alta precisión.
Los conjuntos de campos
El estilo y el diseño predeterminados de una <fieldset>
con un <legend>
son únicos:
Por lo general, para espaciar mis elementos secundarios, usaría la propiedad gap
, pero el posicionamiento único de <legend>
dificulta la creación de un conjunto de elementos secundarios espaciado uniformemente. En lugar de gap
, se usan el selector del mismo nivel adyacente y margin-block-start
.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
De esta manera, se evita que <legend>
se ajuste su espacio mediante la orientación solo a los elementos secundarios <div>
.
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. Unir texto es genial, pero la desalineació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 en la que se muestra cómo la marca de verificación se alinea con la primera línea de texto en una situación de ajuste de varias líneas.](https://web.dev/static/articles/building/a-multi-select-component/image/screenshot-showing-the-c-b135285ef7d8a.png?authuser=6&hl=es)
La cuadrícula animada
Isotope realiza la animación de diseño. Un complemento potente y de alto rendimiento para ordenar y filtrar de manera interactiva.
JavaScript
Además de ayudar a organizar una buena cuadrícula interactiva y animada, JavaScript se usa para pulir un par de asperezas.
Normalización de las entradas del usuario
Este diseño tiene un formulario con dos formas diferentes de proporcionar entrada y no serializan la misma información. Sin embargo, con JavaScript, podemos normalizar los datos.
Elegí alinear la estructura de datos del elemento <select>
con la estructura de las casillas de verificación agrupadas. Para ello, se agrega un objeto de escucha de eventos input
al elemento <select>
, en cuyo punto se asigna 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, es seguro enviar el formulario o, en el caso de esta demostración, indicarle a Isotope qué filtrar.
Finalización del elemento de rol de estado
El elemento solo cuenta y anuncia el recuento de filtros en función de la interacción de las casillas de verificación, pero me parece que era una buena idea compartir además la cantidad de resultados y asegurarse de que también se contaran las opciones del elemento <select>
.
La elección del elemento <select>
se refleja en el 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 conoce el número de filtros elegidos y la cantidad de resultados de esos filtros. Los valores se pueden pasar al elemento de función 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 la función de estado, pero carece de visibilidad para la cantidad de resultados filtrados.
JavaScript puede buscar 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".
Ahora, nuestra excelente experiencia de tecnología de accesibilidad se ofrecerá a todos los usuarios, sin importar cómo interactúen con ella.
Conclusión
Ahora que sabes cómo lo hice, ¿cómo lo harías 🙂
Diversifiquemos nuestros enfoques y aprendamos todas las formas de desarrollar en la Web. Crea una demostración, twittea vínculos y la agregaré a la sección de remixes de la comunidad a continuación.
Remixes de la comunidad
Aún no hay nada que ver aquí.