Parfois pour tes ambitieux besoins UI/UX, tu voudrais ajouter une liste d'éléments que l'utilisateur peut sélectionner (un ou plusieurs).
Normalement il ressemble à ça (c'est pas une image c'est le vrai truc, je précise au cas où):
Les gens appellent parfois ça un "dropdown" ou un "combo box" pour les vieux de l'époque du Visual Basic, avec le soucis qu'en HTML on peut avoir un "dropdown" dans d'autres situations, par exemple un <input type="text"> avec autocomplétion ou un menu de navigation.
L'élément HTML <select> est censé autoriser un affichage de type "liste" avec ces deux fantastiques arguments:
- size — Censé déployer la liste pour qu'elle affiche le nombre d'éléments mentionnés sauf que les navigateurs ne sont pas obligés de montrer ce nombre d'éléments et ne le font juste pas sur mobile;
- multiple — Permet de sélectionner plusieurs éléments. Aussi censé déployer la liste sauf que c'est pas le cas sur mobile non plus. Et alors aussi parfois on a pas envie de permettre la sélection multiple.
Par défaut l'élément select ne permet de sélectionner qu'une seule option, et avec juste l'argument size on peut obtenir une "listbox":

Sauf que sur mobile, l'apparence reste celle d'une boîte déroulante qui affiche un menu système de sélection quand on clique dessus. En gros l'argument size ne sert absolument à rien sur mobile, et personne ne l'explique comme ça, c'est toujours en mode politiquement correct "ouaiii tu vois dans la spec spa obligéééé".

L'argument multiple n'aide pas non plus, il a le même résultat à part que la fenêtre qu'il ouvre quand on tape sur le select comporte des cases à cocher.
Ce qui nous amène à la première constatation que même la doc officielle est d'accord avec moi:
- Pour créer une listbox à sélection unique on peut simplement aligner tout un tas de boutons "radio";
- Pour la même à sélection multiple, on peut utiliser des checkboxes;
- Le mobile first c'est vraiment horrible.
Et en fait si on veut un "listbox" qui affiche aussi plusieurs éléments sur mobile, on a pas le choix il faut créer un élément personnalisé, le select de base ne suffit pas et apparemment tout le monde s'en fout et trouve que c'est normal. Okay. Bienvenue dans ma vie.
L'idée du listbox au niveau UI est le sujet de plusieurs pages de doc dans le contexte de l'accessibilité (pour ceux qui savent pas, c'est pour rendre le web utilisable par les mal-voyants ou des gens qui n'ont l'usage que d'un (voire 0) membre, ce genre de choses), qui est typiquement le truc qui va faire défaut quand on se lance de la création d'un élément personnalisé.
Il y a d'ailleurs un gros avertissement sur la page principale sur ARIA (l'accessibilité dans le web, en gros) qui dit d'éviter de créer des éléments personnalisés quand des éléments natifs existent:

Effectivement si on crée un listbox on va être obligés d'émuler certains comportements avec du JavaScript.
Je proposerais de commencer avec un élément <ul>, puisque c'est une liste après tout.
On peut lui virer le style et padding qui vient par défaut sur les listes à puces, lui coller un overflow-y pour avoir une barre de défilement, et on arriverait à quelque chose comme:
.listbox {
overflow-y: auto;
list-style: none;
padding-left: 0;
background: #fff;
border: solid 1p #333;
}
Pour ce qui est de l'élément ul, il s'agit bien de lui mettre le role "listbox" et un tabindex qui permettra de lui donner le focus. Je pense. J'ai sucé ça de l'air ambiant.
<ul class="listbox" role="listbox" tabindex="0">
Pour l'équivalent des éléments option du select, quelqu'un m'a suggéré le plan brillant d'utiliser des checkboxes (comme c'est d'ailleurs recommandé), les rendre invisibles par opacité, et coller un label devant qui permettra de les sélectionner et les styler avec un sélecteur de voisinage, qui est un peu la base de tout ce qu'on peut faire d'intéressant sans JavaScript dans un navigateur.
J'en ai déjà parlé de nombreuses fois sur ce blog, notamment dans le cadre la question de si HTML/CSS sont des langages de programmation et pour réaliser des trucs et machins interactifs sans JavaScript.
On pourrait utiliser des styles du genre:
.listbox input[type="radio"] {
position: absolute;
opacity: 0;
height: 0;
width: 0;
}
.listbox input[type="radio"]:checked + label {
background: #cecece;
}
.listbox input[type="radio"]:checked:focus + label {
background: #555;
color: #fff;
}
.listbox label {
display: block;
cursor: pointer;
padding: .2rem .4rem;
user-select: none;
font-size: 0.9rem;
}
Avec les couleurs à modifier selon vos préférences. La propriété user-select mise à none permet d'empêcher que le texte se sélectionne lors d'un double click. C'est bête mais peu d'exemples de listbox personnalisées implémentent ce détail.
Les options seraient alors (avec le role qui va bien):
<li role="option">
<input type="radio" name="listbox[]" value="7" id="listitem7"><label for="listitem7">Option 8</label>
</li>
Une fois qu'on a ça, les touches haut et bas fonctionnent de base, ce qui est trop bien. Par contre, la barre de défilement ne descend (ou monte) pas toute seule si on sélectionne une option hors de vue avec le clavier.
Bon... Reste plus qu'à implémenter ça en JavaScript. J'ai utilisé scrollIntoView() qui est bien pratique, en ajoutant des événements sur les touches haut et bas.
const selectables = document.querySelectorAll('.listbox input[type="radio"]')
const labels = document.querySelectorAll('.listbox label')
const listbox = document.querySelector('.listbox')
let selectedItem = 0
selectables.forEach((s, i) => s.addEventListener('focus', () => selectedItem = i))
listbox.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp') {
const previousEl = selectedItem - 1
labels[previousEl >= 0 ? previousEl : labels.length - 1].scrollIntoView(true)
} else if (e.key === 'ArrowDown') {
const nextEl = selectedItem + 1
labels[nextEl < labels.length ? nextEl : 0].scrollIntoView(false)
}
})
Aussi, je sais pas ce qui m'a pris mais j'ai décidé de faire en sorte que quand on arrive tout en haut ou tout en bas de la liste, les touches clavier font passer à l'opposé de la liste. L'élement Select n'a pas ce comportement et il n'est pas recommandé par la doc ARIA.
Par contre, dans l'exemple ARIA, leur code de bandage pour que le défilement fonctionne au clavier est plus indigeste et utilise scrollTop:
/**
* Check if the selected option is in view, and scroll if not
*/
aria.Listbox.prototype.updateScroll = function () {
var selectedOption = document.getElementById(this.activeDescendant);
if (
selectedOption &&
this.listboxNode.scrollHeight > this.listboxNode.clientHeight
) {
var scrollBottom =
this.listboxNode.clientHeight + this.listboxNode.scrollTop;
var elementBottom = selectedOption.offsetTop + selectedOption.offsetHeight;
if (elementBottom > scrollBottom) {
this.listboxNode.scrollTop =
elementBottom - this.listboxNode.clientHeight;
} else if (selectedOption.offsetTop < this.listboxNode.scrollTop) {
this.listboxNode.scrollTop = selectedOption.offsetTop;
}
}
};
Bizarrement, leur exemple n'utilise pas de checkboxes/radio et comprend BEAUCOUP de JavaScript.
J'ai tout de même la contrainte que le label doit être juste après le checkbox/radio et doit avoir l'attribut "for" qui pointe vers le bon élément:
<input type="radio" name="listbox[]" value="7" id="listitem7"><label for="listitem7">Option 8</label>
Dans le turfu il devrait être possible d'utilishttps://dkvz.euer le sélecteur has (encore expérimental pour Firefox, entre autres) pour remplacer le pseudo-sélecteur de frère/soeur direct + et pouvoir mettre le checkbox/radio à l'intérieur du label, ce qui serait plus pratique.
Je vous laisse mon Codepen d'expérimentation si vous voulez avoir une démo ou accès à tout le code:
Commentaires
Il faut JavaScript activé pour écrire des commentaires ici
#1
#2