Astuces CSS avec checkbox
C'est incroyable ce qu'on peut faire avec un élément aussi modeste que les cases à cocher... du moins lorsqu'on s'appelle Will Boyd et qu'on a de la créativité à revendre !
Les checkboxes (cases à cocher) sont épatantes, surtout quand vous les combinez avec un CSS astucieux. Cet article vise à montrer quelques petites choses créatives qu’on peut réaliser avec les checkboxes, et sans JavaScript.
La formule de base
Tout commence avec un peu de HTML :
//HTML
<input id="toggle" type="checkbox">
<label for="toggle">
Rien de bien spécial ici. L’attribut for
sur le <label>
correspond à l’id
qu’on retrouve sur <input>
, par conséquent lorsqu’on clique sur <label>
on bascule l’état de la case à cocher <input>
. C’est important parce que la prochaine étape va consister à cacher la case <input>
.
//CSS
input {
position: absolute;
left: -9999px;
}
Pourquoi ne pas utiliser display: none
? Parce qu’il serait ignoré par les lecteurs d’écran et la navigation par tabulation. La méthode de décalage de la position permet de garder <input>
dans le circuit, mais hors écran.
Cacher <input>
nous facilite le travail. Nous devons toutefois transmettre l’état coché/non coché, ce que nous pouvons faire avec <label>
. Et c’est ici qu’on commence à s’amuser.
//CSS
input:checked + label {
/* nos styles */
}
Nous utilisons un mélange de pseudo-classe :checked et de combinateur adjacent (adjacent sibling) pour dire ceci : “quand la checkbox est cochée (checked), trouver le <label>
immédiatement consécutif et appliquer nos styles”. Vous pouvez même utiliser des pseudo-éléments (:before et :after) à l’intérieur de <label>
pour plus de créativité encore.
//CSS
input:checked + label::before {
/* styles pour un indicateur "on" */
}
Très bien. Voyons tout cela en action. Cette démo utilise la formule basique dont nous venons de parler pour transformer les cases à cocher habituelles en quelque chose de plus impressionnant.
Remarquez que ce qu’il y a de bien, c’est que ça reste des checkboxes. Utilisez-les dans un formulaire et elles le soumettront exactement comme il faut : nous avons modifié leur apparence, pas leur comportement.
Cacher et montrer du contenu
Jusqu’ici, nous avons simplement appliqué quelques styles à <label>
mais nous pouvons aller plus loin que cela. Cette démo cache et révèle dynamiquement des sections du formulaire en fonction des choix de l’utilisateur.
La pseudo-classe :checked
fonctionne avec les boutons radio de la même façon qu’avec les cases à cocher. Voici le code HTML correspondant aux boutons radio “Comment avez-vous entendu parler de nous ?” (How did you hear about us?) :
//HTML
<input id="how-friend" name="how" type="radio">
<label for="how-friend" class="side-label">From a friend</label>
<input id="how-internet" name="how" type="radio">
<label for="how-internet" class="side-label">Somewhere on the internet</label>
<input id="how-other" name="how" type="radio">
<label for="how-other" class="side-label">Other...</label>
<div class="how-other-disclosure">
<label for="how-other-explain" class="top-label">Please explain</label>
<textarea id="how-other-explain"></textarea>
</div>
Le style des boutons radio est réalisé dans <label>
via une combinaison de :before
(pour le cercle externe) et :after
(pour le point vert). Révéler ou cacher :after
lorsqu’un bouton radio est coché ou décoché est très facile :
//CSS
.side-label::after {
display: none;
/* autres styles */
}
input:checked + .side-label::after {
display: block;
}
La <div>
est cachée jusqu’à ce que le bouton radio “Other...” soit coché. Cette fois-ci, la <div>
est cachée via display: none
car je veux que le contenu soit ignoré par les lecteurs d’écran jusqu’à ce qu’il soit effectivement révélé. Le CSS permettant de révéler le contenu de la <div>
lorsque le bouton radio est coché est le suivant :
//CSS
#how-other:checked ~ .how-other-disclosure {
display: block;
}
Jusqu’à présent, nous avons utilisé le combinateur adjacent +
(plus), mais il est temps d’introduire le combinateur général ~
(tilde). Il fonctionne un peu de la même manière, mais sur des frères (siblings) non adjacents, comme notre <div>
.
Arborescence de dossiers
Nous pouvons réutiliser les techniques de la démo précédente pour créer un widget d’arborescence de dossiers.
Le HTML d’un dossier individuel est donné ci-dessous. Le <label>
est le dossier et les deux éléments <a>
sont les “fichiers” à l’intérieur.
//HTML
<div>
<input id="n-1" type="checkbox">
<label for="n-1">Blue</label>
<div class="sub">
<a href="#link">Mana Leak</a>
<a href="#link">Time Warp</a>
</div>
</div>
Des icônes de Font Awesome sont utilisées pour indiquer l’état checked (ouvert) ou unchecked (fermé).
//CSS
label::before, a::before {
display: block;
position: absolute;
top: 6px;
left: -25px;
font-family: ’FontAwesome’;
}
label::before {
content: ’\f07b’; /* closed folder */
}
input:checked + label::before {
content: ’\f07c’; /* open folder */
}
a::before {
content: ’\f068’; /* dash */
}
Le contenu situé à l’intérieur d’un dossier est montré ou caché via le combinateur général ~
. C’est pourquoi on a une <div>
supplémentaire pour envelopper chaque dossier, afin d’éviter que le combinateur ne se propage son effet et n’ouvre les autres dossiers.
//CSS
input:checked ~ .sub {
display: block;
}
Évidemment, les dossiers peuvent être imbriqués. Cliquez sur le dossier “Multicolor” pour en voir un exemple.
Enfin, disons un mot sur notre bouton Reset.
//HTML
<input type="reset" value="Collapse All">
Les boutons de Reset de formulaires ne sont plus vraiment utilisés de nos jours, mais nous avons ici un bon cas d’usage. Si nous cliquons dessus, toutes les cases à cocher retournent à leur état initial non-coché, ce qui ferme tous les dossiers. Cool !
Listes scindées
Cette démo partage les items en deux listes distinctes, selon qu’ils sont cochés ou non.
Le HTML ressemble à ceci :
//HTML
<div class="items">
<input id="item1" type="checkbox" checked>
<label for="item1">Create a to-do list</label>
<!-- more items -->
<h2 class="done" aria-hidden="true">Done</h2>
<h2 class="undone" aria-hidden="true">Not Done</h2>
</div>
Le mécanisme de séparation des listes est obtenu via Flexbox. Voici le CSS correspondant :
//CSS
.items {
display: flex;
flex-direction: column;
}
.done {
order: 1;
}
input:checked + label {
order: 2;
}
.undone {
order: 3;
}
label {
order: 4;
}
CSS Flexbox nous permet de réordonner directement les éléments grâce à la propriété order
. La valeur de la propriété passe de 4
à 2
lorsque la checkbox est cochée, ce qui la déplace de la partie inférieure “Not Done” vers la partie supérieure “Done”.
Malheureusement, la navigation via le clavier ainsi que de nombreux lecteurs d’écran suivront l’ordre des éléments dans le DOM, même s’ils ont été visuellement réordonnés avec flexbox. Du coup, les titres “Done” et “Not Done” sont sans utilité pour les lecteurs d’écran — c’est pourquoi j’ai ajouté aria-hidden="true"
pour eux, mieux vaut qu’ils soient ignorés plutôt que de créer de la confusion. À part cela, la liste scindée est opérationnelle via le clavier et les lecteurs d’écran annonceront l’état d’un item (checked/unchecked).
Si vous êtes intrigués par les compteurs à droite de “Done” et “Not Done”, ils sont générés par des CSS counters, voyez cet article pour plus de détails.
Filtrage de groupes
C’est notre dernière démo. Nous allons voir comment mettre en valeur un tri croisé de données correspondant à un critère de sélection.
Voici le HTML abrégé. Remarquez que l’attribut data-teams
est une liste d’attributs id
de boutons radio séparés par un espace. C’est ainsi qu’on fait correspondre les personnages et les équipes.
//HTML
<input id="original" type="radio" name="team" checked>
<label for="original">Original X-Men</label>
<!-- autres équipes ici -->
<br>
<ul class="characters">
<li id="angel" data-teams="original force factor hellfire">
<h2>Angel</h2>
<img src="ct-angel.png" alt="">
</li>
<!-- autres personnages ici -->
</ul>
En ce qui concerne l’accessibilité, j’utilise des attributs alt
vides parce que les noms de personnages sont déjà dans les balises <h2>
— il n’est donc pas nécessaire, ni souhaitable, de les répéter. De plus, puisque je ne cache pas les éléments <img>
(ils sont seulement rétrécis et décolorés), cela permet aux lecteurs d’écrans de “sauter” les personnages non sélectionnés, il me suffit de cacher la balise <h2>
.
Voici le CSS qui fait ressortir les personnages lorsque leur équipe est sélectionnée :
//CSS
#original:checked ~ .characters [data-teams~="original"] h2,
#force:checked ~ .characters [data-teams~="force"] h2,
#factor:checked ~ .characters [data-teams~="factor"] h2,
#hellfire:checked ~ .characters [data-teams~="hellfire"] h2 {
/* styles to show character name */
}
#original:checked ~ .characters [data-teams~="original"] img,
#force:checked ~ .characters [data-teams~="force"] img,
#factor:checked ~ .characters [data-teams~="factor"] img,
#hellfire:checked ~ .characters [data-teams~="hellfire"] img {
/* styles to show character avatar */
}
Ok, je reconnais que ces sélecteurs sont un peu touffus, mais en fait ce n’est pas si compliqué. Analysons la ligne 1 par exemple. Si on l’exprime en bon français, ça donne ceci : lorsque l’élément ayant une id
‘original’ est coché, chercher le ou les éléments frères (general sibling) ayant la classe de ‘personnages’ (characters) et contenant un attribut data-teams
dans lequel se trouve ‘original’, puis aller chercher la balise <h2>
située à l’intérieur.
On répète avec ‘force’, ‘factor’ et ‘hellfire’ dans les lignes 2 à 4. Enfin, on reproduit le schéma dans le second bloc, mais cette fois-ci pour <img>
à la place de <h2>
.
En guise de conclusion
J’espère que vous avez eu autant de plaisir à jouer avec ces démos que j’en ai eu à les créer. C’était une expérience très intéressante de voir qu’on pouvait tirer quelque chose d’un élément aussi modeste que les cases à cocher. Je n’ai rien contre l’utilisation de JavaScript, lorsqu’elle est appropriée, mais c’est sympa de voir tout ce qu’on peut accomplir sans JS. Merci d’avoir lu cet article !