Creare un componente a selezione multipla

Una panoramica di base su come creare un componente a selezione multipla, adattabile, adattivo e accessibile per ordinare e filtrare le esperienze utente.

In questo post vorrei condividere con voi la possibilità di creare un componente a selezione multipla. Prova la demo.

Demo

Se preferisci i video, ecco una versione di YouTube di questo post:

Panoramica

Agli utenti vengono spesso presentati degli elementi, a volte molti elementi, e in questi casi può essere una buona idea fornire un modo per ridurre l'elenco ed evitare il sovraccarico di scelte. Questo post del blog esplora l'interfaccia utente di filtro come un modo per ridurre le scelte. Per farlo, presenta gli attributi degli elementi che gli utenti possono selezionare o deselezionare, riducendo i risultati e di conseguenza il sovraccarico di scelte.

Interazioni

L'obiettivo è consentire un attraversamento rapido delle opzioni di filtro per tutti gli utenti e i vari tipi di input. Verrà fornita con una coppia di componenti adattabili e reattivi. Una barra laterale tradizionale di caselle di controllo per computer, tastiere e screen reader, e un elemento <select multiple> per gli utenti touch.

Screenshot di confronto che mostra il colore chiaro e il scuro del computer con una barra laterale di caselle di controllo rispetto a dispositivi mobili iOS e Android con un elemento a selezione multipla.

Questa decisione di usare la selezione multipla integrata per il tocco, e non per il desktop, consente di risparmiare lavoro e lavoro, ma credo che offra esperienze appropriate con meno debito di codice rispetto alla creazione dell'intera esperienza reattiva in un solo componente.

Tocco

Il componente tocco consente di risparmiare spazio e contribuisce alla precisione dell'interazione dell'utente su dispositivi mobili. Consente di risparmiare spazio comprimendo un'intera barra laterale di caselle di controllo in un'esperienza touch in overlay integrata di <select>. L'input è più preciso, grazie all'ampio overlay tattile fornito dal sistema.

Uno screenshot di anteprima dell&#39;elemento a selezione multipla in Chrome su Android, iPhone e iPad. Su iPad e iPhone la funzionalità di selezione multipla è aperta e tutti offrono un&#39;esperienza unica, ottimizzata per le dimensioni dello schermo.

Tastiera e gamepad

Di seguito è riportata una dimostrazione di come utilizzare <select multiple> dalla tastiera.

Questa selezione multipla integrata non può avere uno stile ed è offerta solo in un layout compatto non adatto a presentare molte opzioni. Hai visto come non riesci a vedere la varietà di opzioni in quella piccola scatola? Pur essendo possibile modificare le dimensioni, non è utilizzabile come barra laterale delle caselle di controllo.

Markup

Entrambi i componenti saranno contenuti nello stesso elemento <form>. I risultati di questo modulo, che siano 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 aggregati in un elemento <fieldset> e devono essere assegnati un <legend>. Se l'HTML è strutturato in questo modo, gli screen reader e FormData comprenderanno automaticamente la relazione degli elementi.

<form>
  <fieldset>
    <legend>New</legend>
    … checkboxes …
  </fieldset>
</form>

Una volta attivato il raggruppamento, aggiungi <label> e <input type="checkbox"> per ciascuno dei filtri. Ho scelto di aggregare i miei dati in un elemento <div> in modo che la proprietà CSS gap possa distribuirli in modo uniforme e mantenere l'allineamento quando le etichette sono 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 gli elementi legenda e del set di campi che mostra il colore e il nome dell&#39;elemento.

Componente <select multiple>

Una funzionalità dell'elemento <select> utilizzata raramente è multiple. Quando l'attributo viene utilizzato in combinazione con un elemento <select>, l'utente può sceglierne molti dall'elenco. È come cambiare l'interazione da un elenco di pulsanti 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 elemento <select>, utilizza l'elemento <optgroup> e assegnagli un attributo e un valore label. Questo elemento e 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 su desktop di un elemento a selezione multipla.

Monitoraggio dell'input con contatori per informare le tecnologie per la disabilità

La tecnica del ruolo di stato viene utilizzata in questa esperienza utente per monitorare e mantenere il conteggio dei filtri per screen reader e altre tecnologie per la disabilità. Il video di YouTube mostra la funzionalità. L'integrazione inizia con il codice 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 su un elemento principale degli input e dell'elemento di stato.

aside {
  counter-reset: filters;
}

Per impostazione predefinita, il conteggio sarà 0, il che è ottimo. Per impostazione predefinita, in questo design non è nullo nulla è :checked.

Successivamente, per incrementare il contatore appena creato, sceglieremo come target gli elementi secondari dell'elemento <aside> che sono :checked. Quando l'utente cambia lo stato degli input, viene conteggiato il contatore filters.

aside :checked {
  counter-increment: filters;
}

CSS ora è a conoscenza del conteggio generale dell'interfaccia utente delle caselle 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 di pseudo elementi:

aside #applied-filters::before {
  content: counter(filters) " filters ";
}

Nel codice HTML dell'elemento del ruolo di stato ora verrà annunciato "2 filtri" a uno screen reader. Questo è un buon inizio, ma possiamo fare di meglio, ad esempio condividere il conteggio dei risultati aggiornati dai filtri. Questa operazione verrà eseguita da JavaScript, perché non è applicabile ai contatori.

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

Emozione nidificata

L'algoritmo dei contatori sembrava ottimo con CSS nesting-1, dato che ho potuto inserire tutta la logica in un unico 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

In questa sezione vengono descritti i layout tra i due componenti. La maggior parte degli stili di layout è per il componente desktop con caselle di controllo.

Il modulo

Per ottimizzare leggibilità e leggibilità per gli utenti, il modulo ha una larghezza massima di 30 caratteri, in sostanza impostando una larghezza ottica della linea per ogni etichetta del filtro. Il modulo utilizza il layout a griglia e la proprietà gap per distribuire i set di campi.

form {
  display: grid;
  gap: 2ch;
  max-inline-size: 30ch;
}

Elemento <select>

Sia l'elenco di etichette sia le caselle di controllo occupano troppo spazio sui dispositivi mobili. Pertanto, il layout controlla per vedere il dispositivo di puntamento principale dell'utente per modificare l'esperienza del tocco.

@media (pointer: coarse) {
  select[multiple] {
    display: block;
  }
}

Un valore coarse indica che l'utente non sarà in grado di interagire con lo schermo in modo estremamente preciso con il dispositivo di input principale. Su un dispositivo mobile, il valore del puntatore è spesso coarse, in quanto l'interazione principale è il tocco. Su un dispositivo desktop, il valore del puntatore è spesso fine, perché di solito è collegato un mouse o un altro dispositivo di input ad alta precisione.

I set di campi

Lo stile e il layout predefiniti di <fieldset> con <legend> sono univoci:

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

Normalmente, per distribuire gli elementi secondari, utilizzerei la proprietà gap, ma il posizionamento univoco di <legend> rende difficile creare un insieme di elementi secondari equidistanti. Al posto di gap, vengono utilizzati il selettore di pari livello e margin-block-start.

fieldset {
  padding: 2ch;

  & > div + div {
    margin-block-start: 2ch;
  }
}

In questo modo, non è possibile modificare lo spazio di <legend> scegliendo come target solo <div> elementi secondari.

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

L'etichetta del filtro e la casella di controllo

Come elemento secondario diretto di un elemento <fieldset> e all'interno della larghezza massima del 30ch del modulo, il testo dell'etichetta potrebbe essere a capo se troppo lungo. Il testo a capo è ottimo, ma non ci sono disallineamenti tra testo e casella di controllo. Flexbox è l'ideale.

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 wrapping di più righe.
Scopri di più in questo codepen

La griglia animata

L'animazione del layout è eseguita da Isotope. Plug-in potente e potente per l'ordinamento e i filtri interattivi.

JavaScript

Oltre ad aiutarti a orchestrare una griglia animata e interattiva, JavaScript viene utilizzato per perfezionare qualche dettaglio.

Normalizzazione dell'input utente

Questo design ha un modulo con due diversi modi per fornire input che non serializzano lo stesso. Tuttavia, con JavaScript possiamo normalizzare i dati.

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

Ho scelto di allineare la struttura dei dati dell'elemento <select> alla struttura delle caselle di controllo raggruppate. A questo scopo, all'elemento <select> viene aggiunto un listener di eventi input, a quel punto viene mappato 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 oppure, nel caso di questa demo, istruisci Isotope su cosa filtrare in base a cosa.

Completare l'elemento del ruolo di stato

L'elemento conta solo e annuncia il conteggio dei 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 assicurarsi 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 listener per l'input. Al termine di questa funzione, sono noti il numero di filtri scelti e il numero di risultati per tali filtri. I valori possono essere passati all'elemento ruolo stato come questo.

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 di ruolo dello stato, ma non ha visibilità rispetto al numero di risultati filtrati. JavaScript può controllare 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`
})

Complessivamente, questo lavoro completa l'annuncio "2 filtri per 25 risultati".

Uno screenshot dello screen reader MacOS che annuncia i risultati.

Ora la nostra eccellente esperienza relativa alle tecnologie per la disabilità verrà offerta a tutti gli utenti, indipendentemente dal modo in cui vi interagiscono.

Conclusione

Ora che sai come ci sono riuscito, come faresti? 🙂

Diversifica i nostri approcci e scopriamo tutti i modi per creare sul web. Crea una demo, inviami un tweet con i link e lo aggiungerò alla sezione Remix della community di seguito.

Remix della community

Ancora niente da visualizzare