Creare un componente a selezione multipla

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.

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.

Screenshot di confronto che mostra la modalità chiara e scura del computer con una barra laterale di caselle di controllo rispetto a iOS e Android mobile con un elemento di selezione multipla.

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.

Un screenshot dell&#39;anteprima dell&#39;elemento di selezione multipla in Chrome su Android, iPhone e iPad. Su iPad e iPhone la selezione multipla è attivata e ogni dispositivo offre un&#39;esperienza unica ottimizzata per le dimensioni dello schermo.

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>

Uno screenshot con un overlay informativo per la legenda e gli elementi fieldset, che mostra il colore e il nome dell&#39;elemento.

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>

Uno screenshot del rendering desktop di un elemento con selezione multipla.

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.

Uno screenshot dello screen reader di macOS che annuncia il numero di filtri attivi.

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:

Uno screenshot degli stili predefiniti per un insieme di campi e una legenda.

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>.

Screenshot che mostra la spaziatura dei margini tra gli input, ma non la legenda.

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;
}
Screenshot che mostra come il segno di spunta si allinea alla prima riga di testo in uno scenario di a capo su più righe.
Prova altri giochi in questo Codepen

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.

Screenshot della console JavaScript di DevTools che mostra i risultati dei dati normalizzati dell&#39;obiettivo.

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".

Uno screenshot dello screen reader di macOS che annuncia i 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.