Descripción general básica de cómo compilar un componente de selección múltiple adaptable, responsivo y accesible para las experiencias del usuario de ordenamiento y filtrado.
En esta publicación, quiero compartir ideas sobre cómo crear un componente de selección múltiple. Prueba la demostración.
Si prefieres un video, aquí tienes una versión de este artículo 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 artículos que los usuarios pueden seleccionar o anular su selección, lo que reduce los resultados y, por lo tanto, la sobrecarga de opciones.
Interacciones
El objetivo es permitir que todos los usuarios y sus diferentes tipos de entrada puedan navegar rápidamente por las opciones de filtro. Esto se proporcionará con un par de componentes adaptables y responsivos. Una barra lateral tradicional de casillas de verificación para usuarios de computadoras de escritorio, teclados y lectores de pantalla, y un <select
multiple>
para usuarios de dispositivos táctiles.
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 crear toda la experiencia responsiva en un solo componente.
Tocar
El componente táctil ahorra espacio y ayuda a la precisión de la interacción del usuario en dispositivos móviles. Ahorra espacio al contraer toda una barra lateral de casillas de verificación en una experiencia táctil superpuesta integrada <select>
. Ayuda a mejorar la precisión de la entrada mostrando una gran experiencia de superposición táctil proporcionada por el sistema.
Teclado y gamepad
A continuación, se muestra cómo usar un <select multiple>
desde el teclado.
Este elemento integrado de selección múltiple no se puede personalizar y solo se ofrece en un diseño compacto que no es adecuado para presentar muchas opciones. ¿Ves cómo no se pueden ver todas las opciones en ese cuadro pequeño? Si bien puedes cambiar su tamaño, no es tan útil 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 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 incluirse en un elemento <fieldset>
y tener 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 en su lugar, agrega un <label>
y un <input type="checkbox">
para cada uno de los filtros. Decidí envolver 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 extienden a 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>
Una función que se usa poco del elemento <select>
es multiple
.
Cuando el atributo se usa con un elemento <select>
, el usuario puede elegir varios elementos 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 el 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>
Seguimiento de la entrada con contadores para informar a la tecnología de asistencia
En esta experiencia del usuario, se usa la técnica de rol de estado para hacer un seguimiento del recuento de filtros y mantenerlo para los lectores de pantalla y otras tecnologías de asistencia. El video de YouTube 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 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 principal de los elementos de entrada y de estado.
aside {
counter-reset: filters;
}
De forma predeterminada, el recuento será 0
, lo que es excelente, ya que nada está :checked
de forma predeterminada en este diseño.
A continuación, para aumentar nuestro contador recién creado, 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 incrementará.
aside :checked {
counter-increment: filters;
}
Ahora, CSS conoce el recuento general de la IU de la casilla de verificación, y el elemento de rol 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 del pseudo elemento:
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. Este es un buen comienzo, pero podemos hacerlo mejor, por ejemplo, compartir el recuento de los resultados que actualizaron los filtros. Realizaremos este trabajo desde JavaScript, ya que está fuera de lo que pueden hacer los contadores.
Entusiasmo por la anidación
El algoritmo de contadores funcionó muy bien con CSS nesting-1, ya que pude colocar toda la lógica en un solo 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 casilla de verificación 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 óptico 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 consumen demasiado espacio en dispositivos móviles. Por lo tanto, el diseño verifica el dispositivo de apuntamiento 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 un alto nivel 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 diseño y el diseño predeterminados de un <fieldset>
con un <legend>
son únicos:
Normalmente, 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 espaciados de manera uniforme. En lugar de gap
, se usan el selector de hermanos adyacentes y margin-block-start
.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
Esto evita que el <legend>
ajuste su espacio, ya que solo se segmentan los elementos secundarios <div>
.
La etiqueta y la casilla de verificación del filtro
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 ajuste de texto es excelente, 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;
}

La cuadrícula animada
La animación de diseño se realiza con Isotope. Un complemento potente y de alto rendimiento para la clasificación y el filtrado interactivos.
JavaScript
Además de ayudar a orquestar una cuadrícula interactiva y animada, JavaScript se usa para pulir algunos detalles.
Normaliza la entrada del usuario
Este diseño tiene un formulario con dos formas diferentes de proporcionar entrada, y no se serializan de la misma manera. Sin embargo, con un poco de JavaScript, podemos normalizar los datos.
Decidí 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>
, momento en el que se asignan los 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 o, en el caso de esta demostración, indicarle a Isotope por qué filtrar.
Cómo finalizar el elemento de rol de estado
El elemento solo cuenta y anuncia la cantidad de filtros según la interacción de la casilla de verificación, pero me pareció una buena idea compartir también la cantidad de resultados y asegurarme de que también se cuenten 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 conocen la cantidad de filtros elegidos y la cantidad de resultados para 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 muestra la cantidad filtrada de resultados.
JavaScript puede supervisar 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 arrojan 25 resultados".
Ahora, nuestra excelente experiencia de tecnología de asistencia se brindará 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 crear contenido en la Web. Crea una demostración, envíame por Twitter los vínculos y la agregaré a la sección de remixes de la comunidad que se encuentra a continuación.
Remixes de la comunidad
Aún no hay nada para ver aquí.