Una panoramica di base su come creare un componente di selezione multipla reattivo, adattabile e accessibile per ordinare e filtrare le esperienze utente.
In questo post voglio condividere alcune idee su come creare un componente di selezione multipla. Prova la demo.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
Spesso agli utenti vengono presentati degli articoli, a volte molti, e in questi casi può essere una buona idea fornire un modo per ridurre l'elenco in modo da evitare un sovraffollamento di opzioni. Questo post del blog esplora l'interfaccia utente di filtro come un modo per ridurre le scelte. Per farlo, 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'esplorazione rapida delle opzioni di filtro per tutti gli utenti e i relativi tipi di input diversi. Verrà fornito con una coppia di componenti adattabili e adattabili. Una barra laterale tradizionale di caselle di controllo per computer, tastiera
e screen reader e un <select
multiple>
per gli utenti che utilizzano dispositivi touch.
Questa decisione di utilizzare la selezione multipla integrata per il tocco e non per il desktop consente di risparmiare e creare lavoro, ma ritengo che offra esperienze appropriate con meno debiti di codice rispetto alla creazione dell'intera esperienza adattabile in un unico componente.
Tocco
Il componente tocco consente di risparmiare spazio e migliora l'accuratezza dell'interazione utente su dispositivi mobili. Risparmia spazio comprimendo un'intera barra laterale di caselle di controllo in un'<select>
esperienza touch in overlay integrata. Migliora l'accuratezza dell'input mostrando
un'esperienza di overlay tocco di grandi dimensioni 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 personalizzata ed è disponibile solo in un layout compatto non adatto alla presentazione di molte opzioni. Capisci che non puoi vedere tutta la gamma di opzioni in quella piccola casella? Sebbene sia possibile modificarne le dimensioni, non è ancora così 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, che si tratti di caselle di controllo o di 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 valore
<legend>
.
Quando l'HTML è strutturato in questo modo, gli screen reader e FormData comprendono automaticamente la relazione tra gli elementi.
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
Una volta impostato il raggruppamento, aggiungi un <label>
e un <input type="checkbox">
per ciascuno dei filtri. Ho scelto di racchiuderle in un <div>
in modo che la proprietà gap
CSS possa spaziarle in modo uniforme e mantenere l'allineamento quando le etichette diventano 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à raramente utilizzata dell'elemento <select>
è
multiple
.
Quando l'attributo viene utilizzato con un elemento <select>
, l'utente può scegliere più elementi dall'elenco. È come cambiare l'interazione da un elenco di opzioni 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 questo 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 elementi
<option>
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 fornire informazioni alle tecnologie per la disabilità
La tecnica status
role
viene utilizzata in questa esperienza utente per monitorare e mantenere aggiornato il conteggio dei
filtri per screen reader e altre tecnologie per la disabilità. Il video di YouTube
dimostra la funzionalità. L'integrazione inizia con 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 elementi di input e stato.
aside {
counter-reset: filters;
}
Per impostazione predefinita, il conteggio sarà 0
, il che è ottimo, in quanto in questo design non è impostato alcun valore :checked
per impostazione predefinita.
A questo punto, per incrementare il contatore appena creato, sceglieremo come target gli elementi secondari dell'elemento <aside>
che sono :checked
. Quando l'utente modifica lo stato degli input,
il contatore filters
viene conteggiato.
aside :checked {
counter-increment: filters;
}
CSS ora è a conoscenza del conteggio generale dell'interfaccia utente della casella di controllo e l'elemento del ruolo stato è vuoto e in attesa di valori. Poiché il CSS gestisce il conteggio in memoria, la funzione counter()
consente di accedere al valore dai contenuti dell'elemento
pseudo:
aside #applied-filters::before {
content: counter(filters) " filters ";
}
L'HTML per l'elemento del ruolo di stato ora annuncerà "2 filtri" a uno screen reader. È un buon inizio, ma possiamo fare di più, ad esempio condividere il conteggio dei risultati aggiornati dai filtri. Eseguiremo questo lavoro da JavaScript, poiché non rientra nelle funzionalità dei contatori.
Eccitazione per i nidi
L'algoritmo dei contatori è stato fantastico con CSS nesting-1, perché ho potuto inserire tutta la logica in un blocco. È 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 è destinata al componente di casella di controllo per computer.
Il modulo
Per ottimizzare la leggibilità e la scansione per gli utenti, al modulo viene assegnata una larghezza massima di 30 caratteri, impostando in sostanza una larghezza della riga ottica per ogni etichetta del filtro. Il modulo utilizza il layout a griglia e la proprietà gap
per distribuire gli elementi form.
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 coarse
indica che l'utente non potrà interagire con lo schermo con un'elevata precisione con il suo dispositivo di input principale. Su un
dispositivo mobile, il valore del cursore è spesso coarse
, poiché l'interazione principale
è il tocco. Su un computer, il valore del cursore è spesso fine
perché è comune avere un mouse o un altro dispositivo di input ad 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 con spaziatura uniforme. Al posto di gap
, vengono utilizzati il selettore di fratelli adiacenti e margin-block-start
.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
In questo modo, lo spazio di <legend>
non viene modificato perché scegli come target solo i bambini <div>
.
L'etichetta e la casella di controllo del filtro
Poiché è un elemento secondario diretto di un <fieldset>
e rientra nella larghezza massima del 30ch
del modulo, il testo dell'etichetta potrebbe essere a capo se troppo lungo. Il testo a capo è ottimo, ma il mancato allineamento tra il testo e la casella di controllo non lo è. Flexbox è ideale per questo.
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}
La griglia animata
L'animazione del layout viene eseguita da Isotope. Un plug-in potente e performante per l'ordinamento e i filtri interattivi.
JavaScript
Oltre ad aiutare a orchestrare una griglia animata e interattiva ordinata, JavaScript viene utilizzato per perfezionare un paio di aspetti.
Normalizzazione dell'input dell'utente
Questo design ha un modulo con due diversi modi per fornire input, che non serializzano lo stesso. Tuttavia, con un po' di JavaScript possiamo normalizzare i dati.
Ho scelto di allineare la struttura di dati dell'elemento <select>
alla struttura delle caselle di controllo raggruppate. A questo scopo, viene aggiunto un
input
listener di eventi all'elemento <select>
, a quel punto i suoi
selectedOptions
vengono mappati.
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 in tutta sicurezza oppure, nel caso di questa demo, puoi indicare a Isotope su quali criteri applicare i filtri.
Completare l'elemento del ruolo dello stato
L'elemento conteggia e annuncia solo il numero di filtri in base all'interazione con le caselle 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>
.
La scelta dell'elemento <select>
si riflette in counter()
Nella sezione di normalizzazione dei dati è già stato creato un ascoltatore all'input. Alla fine di questa funzione, sono noti il numero di filtri scelti e il numero di risultati per questi filtri. I valori possono essere passati all'elemento ruolo stato come questo.
let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length
Risultati riflessi nell'elemento role="status"
:checked
fornisce un modo integrato per passare il numero di filtri scelti all'elemento del ruolo dello stato, ma non consente di visualizzare il numero di risultati filtrati.
JavaScript può monitorare le interazioni con le caselle di controllo e, dopo aver filtrato la tabella, 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 forniscono 25 risultati".
Ora la nostra eccellente esperienza con le tecnologie per la disabilità verrà offerta a tutti gli utenti, indipendentemente dal modo in cui interagiscono con essa.
Conclusione
Ora che sai come ho fatto, come faresti? 🙂
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, twittami i link e io la aggiungerò alla sezione dei remix della community di seguito.
Remix della community
Ancora nessun elemento da visualizzare.