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.
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.
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 ya que 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 cómo usar un <select multiple>
desde el teclado.
No se puede aplicar diseño a esta selección múltiple integrada. 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 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 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>
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>
Seguimiento de entradas con contadores para informar a la tecnología de accesibilidad
En esta experiencia del usuario, se utiliza la técnica de la función de estado para realizar un seguimiento y mantener el recuento de los filtros para lectores de pantalla y otras tecnologías de asistencia. El video de YouTube
demuestra esta 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 que es genial, ya que nada es :checked
de forma predeterminada en este diseño.
A continuación, para incrementar el contador recién creado, nos orientaremos a 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. Este es un buen comienzo, pero podemos hacerlo 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.
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 casilla de verificación de escritorio.
El formulario
Para optimizar la legibilidad y la capacidad de escaneo de 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 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 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 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:
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>
.
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;
}
La cuadrícula animada
La animación de diseño se realiza mediante Isotope. Un plugin potente y eficaz 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.
Cómo normalizar la entrada del usuario
Este diseño tiene una forma con dos formas diferentes de proporcionar entrada, y no serializan la misma. Sin embargo, con un poco de JavaScript, podemos normalizar los datos.
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>
y, en ese momento, 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 por qué debe filtrarse.
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>
reflejada 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 de “2 filtros que dan 25 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.