Créer un composant à sélection multiple

Présentation de base sur la façon de créer un composant à sélection multiple réactif, adaptatif et accessible pour les expériences utilisateur de tri et de filtrage.

Dans cet article, je vais vous expliquer comment créer un composant à sélection multiple. Essayez la démonstration.

Démo

Si vous préférez les vidéos, voici une version YouTube de cet article :

Présentation

Les utilisateurs sont souvent confrontés à des éléments, parfois beaucoup d'éléments. Dans ce cas, il peut être judicieux de leur permettre de réduire la liste pour éviter la surcharge de choix. Cet article de blog explore l'interface utilisateur de filtrage comme moyen de réduire les choix. Pour ce faire, il présente des attributs d'articles que les utilisateurs peuvent sélectionner ou désélectionner, ce qui réduit le nombre de résultats et donc la surcharge de choix.

Interactions

L'objectif est de permettre à tous les utilisateurs de parcourir rapidement les options de filtrage, quels que soient leurs types de saisie. Il sera fourni avec une paire de composants adaptables et réactifs. Une barre latérale traditionnelle de cases à cocher pour les ordinateurs, les claviers et les lecteurs d'écran, et un <select multiple> pour les utilisateurs d'écrans tactiles.

Capture d&#39;écran comparant les thèmes clair et sombre sur ordinateur avec une barre latérale de cases à cocher, par rapport à iOS et Android sur mobile avec un élément à sélection multiple.

Cette décision d'utiliser la sélection multiple intégrée pour les écrans tactiles, et non pour les ordinateurs, permet d'économiser du travail et d'en créer, mais je pense qu'elle offre des expériences appropriées avec moins de dette technique que si l'on créait l'ensemble de l'expérience responsive dans un seul composant.

Toucher

Le composant tactile permet de gagner de l'espace et d'améliorer la précision des interactions utilisateur sur mobile. Il permet de gagner de l'espace en réduisant une barre latérale entière de cases à cocher en une expérience tactile de superposition <select> intégrée. Il permet d'améliorer la précision des saisies en affichant une grande zone tactile fournie par le système.

Aperçu d&#39;une capture d&#39;écran de l&#39;élément de sélection multiple dans Chrome sur Android, iPhone et iPad. Sur l&#39;iPad et l&#39;iPhone, la sélection multiple est activée et chaque appareil bénéficie d&#39;une expérience unique optimisée pour la taille de l&#39;écran.

Clavier et manette de jeu

Vous trouverez ci-dessous une démonstration de l'utilisation d'un <select multiple> au clavier.

Cette sélection multiple intégrée ne peut pas être stylisée et n'est proposée que dans une mise en page compacte qui ne convient pas à la présentation de nombreuses options. Vous voyez comme il est difficile de voir l'étendue des options dans cette petite boîte ? Bien que vous puissiez modifier sa taille, il n'est pas aussi pratique qu'une barre latérale de cases à cocher.

Annoter

Les deux composants seront contenus dans le même élément <form>. Les résultats de ce formulaire, qu'il s'agisse de cases à cocher ou d'une sélection multiple, seront observés et utilisés pour filtrer la grille, mais pourront également être envoyés à un serveur.

<form>

</form>

Composant "Cases à cocher"

Les groupes de cases à cocher doivent être encapsulés dans un élément <fieldset> et recevoir un <legend>. Lorsque le code HTML est structuré de cette manière, les lecteurs d'écran et FormData comprennent automatiquement la relation entre les éléments.

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

Une fois le regroupement en place, ajoutez un <label> et un <input type="checkbox"> pour chacun des filtres. J'ai choisi de les envelopper dans un <div> afin que la propriété CSS gap puisse les espacer de manière uniforme et maintenir l'alignement lorsque les libellés sont sur plusieurs lignes.

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

Capture d&#39;écran avec une superposition informative pour les éléments de légende et de fieldset, montrant la couleur et le nom de l&#39;élément.

Composant <select multiple>

multiple est une fonctionnalité rarement utilisée de l'élément <select>. Lorsque l'attribut est utilisé avec un élément <select>, l'utilisateur est autorisé à choisir plusieurs éléments dans la liste. Cela revient à remplacer une liste d'options par une liste de cases à cocher.

<form>
  <select multiple="true" title="Filter results by category">
    …
  </select>
</form>

Pour libeller et créer des groupes dans un <select>, utilisez l'élément <optgroup> et attribuez-lui un attribut et une valeur label. Cet élément et cette valeur d'attribut sont semblables aux éléments <fieldset> et <legend>.

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      …
    </optgroup>
  </select>
</form>

Ajoutez maintenant les éléments <option> pour le filtre.

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

Capture d&#39;écran de l&#39;affichage sur ordinateur d&#39;un élément à sélection multiple.

Suivi des saisies avec des compteurs pour informer les technologies d'assistance

La technique du rôle d'état est utilisée dans cette expérience utilisateur pour suivre et maintenir le nombre de filtres pour les lecteurs d'écran et autres technologies d'assistance. La vidéo YouTube montre comment fonctionne la fonctionnalité. L'intégration commence par HTML et l'attribut role="status".

<div role="status" class="sr-only" id="applied-filters"></div>

Cet élément lit à voix haute les modifications apportées au contenu. Nous pouvons mettre à jour le contenu avec des compteurs CSS à mesure que les utilisateurs interagissent avec les cases à cocher. Pour ce faire, nous devons d'abord créer un compteur avec un nom sur un élément parent des éléments d'entrée et d'état.

aside {
  counter-reset: filters;
}

Par défaut, le nombre est défini sur 0, ce qui est parfait, car rien n'est :checked par défaut dans cette conception.

Ensuite, pour incrémenter le compteur que nous venons de créer, nous allons cibler les enfants de l'élément <aside> qui sont :checked. À mesure que l'utilisateur modifie l'état des entrées, le compteur filters s'incrémente.

aside :checked {
  counter-increment: filters;
}

Le CSS est désormais conscient du nombre total de l'interface utilisateur de la case à cocher, et l'élément de rôle d'état est vide et en attente de valeurs. Comme le CSS conserve le total en mémoire, la fonction counter() permet d'accéder à la valeur à partir du contenu du pseudo-élément :

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

Le code HTML de l'élément de rôle d'état annoncera désormais "2 filtres" à un lecteur d'écran. C'est un bon début, mais nous pouvons faire mieux, par exemple en partageant le nombre de résultats mis à jour par les filtres. Nous allons effectuer cette opération à partir de JavaScript, car elle dépasse les capacités des compteurs.

Capture d&#39;écran du lecteur d&#39;écran macOS annonçant le nombre de filtres actifs.

L'excitation de la nidification

L'algorithme de compteurs fonctionnait très bien avec CSS nesting-1, car j'ai pu mettre toute la logique dans un seul bloc. Il est portable et centralisé pour la lecture et la mise à jour.

aside {
  counter-reset: filters;

  & :checked {
    counter-increment: filters;
  }

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

Mises en page

Cette section décrit les mises en page entre les deux composants. La plupart des styles de mise en page sont destinés au composant de case à cocher pour ordinateur.

Le formulaire

Pour optimiser la lisibilité et la numérisation pour les utilisateurs, la largeur maximale du formulaire est de 30 caractères, ce qui définit essentiellement une largeur de ligne optique pour chaque libellé de filtre. Le formulaire utilise une mise en page en grille et la propriété gap pour espacer les ensembles de champs.

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

L'élément <select>

La liste des libellés et les cases à cocher occupent trop d'espace sur mobile. Par conséquent, la mise en page vérifie le dispositif de pointage principal de l'utilisateur pour modifier l'expérience tactile.

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

Une valeur de coarse indique que l'utilisateur ne pourra pas interagir avec l'écran avec une grande précision à l'aide de son principal périphérique d'entrée. Sur un appareil mobile, la valeur du pointeur est souvent coarse, car l'interaction principale est tactile. Sur un ordinateur, la valeur du pointeur est souvent fine, car il est courant d'avoir une souris ou un autre périphérique de saisie de haute précision connecté.

Les ensembles de champs

La mise en forme et la mise en page par défaut d'un <fieldset> avec un <legend> sont uniques :

Capture d&#39;écran des styles par défaut pour un fieldset et une légende.

Normalement, pour espacer mes éléments enfants, j'utiliserais la propriété gap, mais le positionnement unique de <legend> rend difficile la création d'un ensemble d'enfants espacés de manière uniforme. Au lieu de gap, le sélecteur de frère adjacent et margin-block-start sont utilisés.

fieldset {
  padding: 2ch;

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

Cela permet d'éviter que l'espace de <legend> ne soit ajusté en ciblant uniquement les enfants <div>.

Capture d&#39;écran montrant l&#39;espacement des marges entre les entrées, mais pas la légende.

Libellé et case à cocher du filtre

En tant qu'enfant direct d'un <fieldset> et dans la largeur maximale du 30ch du formulaire, le texte du libellé peut être renvoyé à la ligne s'il est trop long. L'habillage du texte est une bonne chose, mais le décalage entre le texte et la case à cocher ne l'est pas. Flexbox est idéal pour cela.

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
Capture d&#39;écran montrant comment la coche s&#39;aligne sur la première ligne de texte dans un scénario d&#39;habillage sur plusieurs lignes.
Jouez davantage dans ce Codepen

Grille animée

L'animation de mise en page est effectuée par Isotope. Un plug-in performant et puissant pour le tri et le filtrage interactifs.

JavaScript

En plus d'aider à orchestrer une grille animée et interactive, JavaScript est utilisé pour peaufiner quelques détails.

Normaliser l'entrée utilisateur

Cette conception comporte un formulaire avec deux façons différentes de fournir des entrées, et elles ne sont pas sérialisées de la même manière. Toutefois, avec un peu de JavaScript, nous pouvons normaliser les données.

Capture d&#39;écran de la console JavaScript des outils de développement montrant l&#39;objectif et les résultats des données normalisées.

J'ai choisi d'aligner la structure de données de l'élément <select> sur la structure des cases à cocher groupées. Pour ce faire, un écouteur d'événements input est ajouté à l'élément <select>, auquel cas les selectedOptions sont mappés.

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
  }, [])
})

Vous pouvez maintenant envoyer le formulaire ou, dans le cas de cette démo, indiquer à Isotope sur quoi filtrer.

Terminer l'élément de rôle d'état

L'élément ne fait que comptabiliser et annoncer le nombre de filtres en fonction de l'interaction avec les cases à cocher, mais j'ai pensé qu'il était judicieux de partager également le nombre de résultats et de m'assurer que les choix d'éléments <select> sont également comptabilisés.

Le choix de l'élément <select> se reflète dans counter()

Dans la section sur la normalisation des données, un écouteur a déjà été créé sur l'entrée. À la fin de cette fonction, le nombre de filtres choisis et le nombre de résultats pour ces filtres sont connus. Les valeurs peuvent être transmises à l'élément de rôle d'état comme ceci.

let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length

Résultats reflétés dans l'élément role="status"

:checked permet de transmettre le nombre de filtres choisis à l'élément de rôle d'état, mais ne permet pas de voir le nombre de résultats filtrés. JavaScript peut surveiller l'interaction avec les cases à cocher et, après avoir filtré la grille, ajouter textContent comme l'a fait l'élément <select>.

document
  .querySelector('aside form')
  .addEventListener('input', e => {
    // isotope demo code
    let filterResults = IsotopeGrid.getFilteredItemElements().length
    document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})

Ce travail complète l'annonce "2 filtres donnant 25 résultats".

Capture d&#39;écran du lecteur d&#39;écran macOS annonçant les résultats.

Désormais, tous les utilisateurs pourront profiter de notre excellente expérience d'assistance technologique, quelle que soit la façon dont ils interagissent avec elle.

Conclusion

Maintenant que vous savez comment j'ai fait, comment feriez-vous ? 🙂

Diversifions nos approches et découvrons toutes les façons de créer sur le Web. Créez une démo, tweetez-moi les liens et je l'ajouterai à la section des remix de la communauté ci-dessous !

Remix de la communauté

Aucun élément à afficher pour le moment.