Una panoramica di base su come creare un componente multiselezione reattivo, adattivo e accessibile per le esperienze utente di ordinamento e filtro.
In questo post voglio condividere il mio pensiero su un modo per creare un componente di selezione multipla. Prova la demo.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
Agli utenti vengono spesso presentati articoli, a volte molti, e in questi casi può essere una buona idea fornire un modo per ridurre l'elenco per evitare il sovraccarico di scelta. Questo post del blog esplora l'interfaccia utente di filtraggio come modo per ridurre le scelte. A questo scopo, presenta gli attributi degli articoli che gli utenti possono selezionare o deselezionare, riducendo i risultati e quindi il sovraccarico di scelta.
Interazioni
L'obiettivo è consentire l'attraversamento rapido delle opzioni di filtro per tutti gli utenti e i loro
diversi tipi di input. Verrà fornito con una coppia di componenti adattabili e reattivi. Una barra laterale tradizionale di caselle di controllo per desktop, tastiera
e screen reader e un <select
multiple>
per gli utenti che utilizzano il tocco.
Questa decisione di utilizzare la selezione multipla integrata per il tocco e non per il computer salva e crea lavoro, ma ritengo che offra esperienze appropriate con un debito di codice inferiore rispetto alla creazione dell'intera esperienza reattiva in un unico componente.
Tocco
Il componente Tocco consente di risparmiare spazio e migliora la precisione dell'interazione utente su
dispositivo mobile. Consente di risparmiare spazio comprimendo un'intera barra laterale di caselle di controllo in un'esperienza tattile di overlay integrata <select>
. Migliora la precisione dell'input mostrando
una grande esperienza di overlay touch fornita dal sistema.
Tastiera e gamepad
Di seguito è riportata una dimostrazione di come utilizzare un <select multiple>
dalla tastiera.
Questa selezione multipla integrata non può essere stilizzata e viene offerta solo in un layout compatto non adatto a presentare molte opzioni. Vedi come non riesci a vedere l'ampiezza delle opzioni in quella piccola casella? Anche se puoi modificarne le dimensioni, non è comunque utilizzabile come una barra laterale di caselle di controllo.
Segni e linee
Entrambi i componenti saranno contenuti nello stesso elemento <form>
. I risultati di
questo modulo, siano essi caselle di controllo o una selezione multipla, verranno osservati e utilizzati per
filtrare la griglia, ma potrebbero anche essere inviati a un server.
<form>
</form>
Componente Caselle di controllo
I gruppi di caselle di controllo devono essere racchiusi in un elemento
<fieldset>
e devono avere un
<legend>
.
Quando l'HTML è strutturato in questo modo, gli screen reader e
FormData comprenderanno
automaticamente la relazione tra gli elementi.
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
Con il raggruppamento in atto, aggiungi un <label>
e un <input type="checkbox">
per
ciascuno dei filtri. Ho scelto di racchiuderli in un <div>
in modo che la proprietà CSS gap
possa spaziarli in modo uniforme e mantenere l'allineamento quando le etichette vanno su più righe.
<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 funzionalità usata raramente dell'elemento <select>
è
multiple
.
Quando l'attributo viene utilizzato con un elemento <select>
, l'utente può
scegliere più elementi dall'elenco. È come passare da un elenco di pulsanti di opzione
a un elenco di caselle di controllo.
<form>
<select multiple="true" title="Filter results by category">
…
</select>
</form>
Per etichettare e creare gruppi all'interno di un <select>
, utilizza l'elemento
<optgroup>
e assegnagli un attributo e un valore label
. Questo elemento e il valore dell'attributo
sono simili agli elementi <fieldset>
e <legend>
.
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
…
</optgroup>
</select>
</form>
Ora aggiungi gli
<option>
elementi per il 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>
Monitoraggio dell'input con contatori per informare la tecnologia assistiva
In questa esperienza utente viene utilizzata la tecnica del ruolo
stato
per monitorare e mantenere il conteggio dei
filtri per gli screen reader e altre tecnologie per la disabilità. Il video di YouTube
mostra la funzionalità. L'integrazione inizia con l'HTML e l'attributo
role="status"
.
<div role="status" class="sr-only" id="applied-filters"></div>
Questo elemento leggerà ad alta voce le modifiche apportate ai contenuti. Possiamo aggiornare i contenuti con i contatori CSS man mano che gli utenti interagiscono con le caselle di controllo. Per farlo, dobbiamo prima creare un contatore con un nome in un elemento padre degli input e dell'elemento di stato.
aside {
counter-reset: filters;
}
Per impostazione predefinita, il conteggio sarà 0
, il che è ottimo, in questo progetto non è :checked
nulla per impostazione predefinita.
Successivamente, per incrementare il contatore appena creato, selezioneremo gli elementi secondari dell'elemento
<aside>
che sono :checked
. Man mano che l'utente modifica lo stato degli input,
il contatore filters
aumenterà.
aside :checked {
counter-increment: filters;
}
CSS ora riconosce il conteggio generale dell'interfaccia utente della casella di controllo e l'elemento del ruolo di stato
è vuoto e in attesa di valori. Poiché CSS mantiene il conteggio in
memoria, la
funzione
counter()
consente di accedere al valore dai contenuti dello
pseudo elemento:
aside #applied-filters::before {
content: counter(filters) " filters ";
}
Il codice HTML dell'elemento del ruolo di stato ora annuncia "2 filtri" a un lettore di schermo. Questo è un buon inizio, ma possiamo fare di meglio, ad esempio condividere il conteggio dei risultati aggiornati dai filtri. Eseguiremo questa operazione da JavaScript, in quanto non rientra nelle funzionalità dei contatori.
L'emozione dell'annidamento
L'algoritmo dei contatori è stato perfetto con CSS nesting-1, perché ho potuto inserire tutta la logica in un unico blocco. Sembra portatile e centralizzato per la lettura e l'aggiornamento.
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
& #applied-filters::before {
content: counter(filters) " filters ";
}
}
Layout
Questa sezione descrive i layout tra i due componenti. La maggior parte degli stili di layout è per il componente casella di controllo desktop.
Il modulo
Per ottimizzare la leggibilità e la scansione per gli utenti, al modulo viene assegnata una larghezza massima di 30 caratteri, impostando essenzialmente una larghezza di riga ottica per ogni etichetta di filtro. Il modulo utilizza il layout a griglia e la proprietà gap
per distanziare i
fieldset.
form {
display: grid;
gap: 2ch;
max-inline-size: 30ch;
}
Elemento <select>
L'elenco di etichette e caselle di controllo occupa troppo spazio sui dispositivi mobili. Pertanto, il layout controlla il dispositivo di puntamento principale dell'utente per modificare l'esperienza per il tocco.
@media (pointer: coarse) {
select[multiple] {
display: block;
}
}
Un valore di coarse
indica che l'utente non potrà interagire con
lo schermo con un elevato livello di precisione con il dispositivo di input principale. Su un
dispositivo mobile, il valore del puntatore è spesso coarse
, poiché l'interazione
principale è il tocco. Su un computer, il valore del puntatore è spesso fine
, poiché è comune
avere un mouse o un altro dispositivo di input di alta precisione collegato.
I fieldset
Lo stile e il layout predefiniti di un <fieldset>
con un <legend>
sono unici:
Normalmente, per spaziare gli elementi secondari utilizzerei la proprietà gap
, ma il posizionamento
unico di <legend>
rende difficile creare un insieme di elementi secondari
equamente distanziati. Al posto di gap
, vengono utilizzati il selettore
di elementi adiacenti e
margin-block-start
.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
In questo modo, lo spazio di <legend>
non viene modificato scegliendo come target solo gli elementi secondari
<div>
.
L'etichetta e la casella di controllo del filtro
In qualità di elemento secondario diretto di un <fieldset>
e all'interno della larghezza massima del 30ch
del modulo, il testo dell'etichetta potrebbe andare a capo se troppo lungo. Il ritorno a capo è ottimo, ma
l'allineamento errato tra testo e casella di controllo no. Flexbox è ideale per questo scopo.
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}

La griglia animata
L'animazione del layout viene eseguita da Isotope. Un plug-in performante e potente per l'ordinamento e il filtro interattivi.
JavaScript
Oltre a contribuire a orchestrare una griglia animata e interattiva, JavaScript viene utilizzato per perfezionare alcuni dettagli.
Normalizzazione dell'input utente
Questo design ha un modulo con due modi diversi per fornire input e non serializza lo stesso. Con un po' di JavaScript, però, possiamo normalizzare i dati.
Ho scelto di allineare la struttura dei dati dell'elemento <select>
alla struttura delle caselle di controllo raggruppate. A questo scopo, al tag <select>
viene aggiunto un
input
listener di eventi, a quel punto vengono mappati
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
}, [])
})
Ora puoi inviare il modulo o, nel caso di questa demo, indicare a Isotope in base a cosa filtrare.
Completamento dell'elemento ruolo stato
L'elemento conteggia e annuncia solo il numero di filtri in base all'interazione con la casella di controllo, ma ho pensato che fosse una buona idea condividere anche il numero di risultati e assicurarmi che vengano conteggiate anche le scelte dell'elemento <select>
.
Scelta dell'elemento <select>
visualizzata in counter()
Nella sezione di normalizzazione dei dati, è già stato creato un listener sull'input. Alla fine di questa funzione, il numero di filtri scelti e il numero di risultati per questi filtri sono noti. I valori possono essere passati all'elemento del ruolo di stato in questo modo.
let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length
Risultati riportati nell'elemento role="status"
:checked
fornisce un modo integrato per passare il numero di filtri scelti all'elemento del ruolo di stato, ma non consente di visualizzare il numero di risultati filtrati.
JavaScript può monitorare l'interazione con le caselle di controllo e, dopo aver filtrato la
griglia, aggiungere textContent
come ha fatto l'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`
})
Nel complesso, questo lavoro completa l'annuncio "2 filtri che restituiscono 25 risultati".
Ora la nostra eccellente esperienza di tecnologia assistiva verrà offerta a tutti gli utenti, indipendentemente dal modo in cui interagiscono con essa.
Conclusione
Ora che sai come ho fatto, come faresti tu?‽ 🙂
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, inviami un tweet con i link e la aggiungerò alla sezione dei remix della community qui sotto.
Remix della community
Ancora nessun elemento da visualizzare.