I. Le besoin de listes déroulantes

Image non disponible
Voir une démo en ligne

Le rendu par défaut des navigateurs n'a jamais été idéal, c'est pourquoi les développeurs Web essayent constamment de repousser les limites lorsqu'il s'agit de donner des styles aux éléments HTML natifs. Récemment, nous avons vu apparaître de nombreux exemples de boutons radio ou de cases à cocher personnalisés, de barres de progression et beaucoup d'autres éléments, mais rien concernant les listes déroulantes ou les champs de type fichiers.

Je me suis donc inspiré de la technique présentée par Lea Verou pour apporter ma contribution et partager avec vous ma version de listes déroulantes personnalisées.

II. Le balisage

Pour commencer, voici le code HTML de ma liste déroulante.

 
Sélectionnez
<span class="custom-dropdown custom-dropdown--white">
    <select class="custom-dropdown__select custom-dropdown__select--white">
        <option>The Shawshank Redemption</option>
        <option>The Godfather</option>
        <option>Pulp Fiction</option>
        <option>The Good, the Bad and the Ugly</option>
        <option>12 Angry Men</option>
    </select>
</span>

Vous devez vous demander pourquoi j'ai choisi une balise <span> comme conteneur au lieu d'une balise <label> qui semblerait plus adaptée. La raison est que je ne veux pas perdre la fonctionnalité d'un label juste pour de la mise en forme. Ainsi, vous pouvez toujours englober ce code dans une balise <label> pour améliorer l'ergonomie comme ceci :

 
Sélectionnez
<label>
    IMDB Top Movies:
    <span class="custom-dropdown custom-dropdown--white">
        <select class="custom-dropdown__select custom-dropdown__select--white">
            <option>The Shawshank Redemption</option>
            <option>The Godfather</option>
            <option>Pulp Fiction</option>
            <option>The Good, the Bad and the Ugly</option>
            <option>12 Angry Men</option>
        </select>
    </span>
</label>

Convention de nommage BEM

Vous avez certainement remarqué le nommage particulier. J'utilise la convention de nommage BEMBlock-Element-Modifier que vous avez déjà certainement rencontrée. Inutile de préciser que c'est top !

III. Le CSS

Image non disponible

Étudiez le code ci-dessous pour voir comment tout cela fonctionne. La règle @supports est celle qui apporte toute la magie :

 
Sélectionnez
.custom-dropdown--large {
    font-size: 1.5em;
}

.custom-dropdown--small {
    font-size: .7em;
}

.custom-dropdown__select{
    font-size: inherit; /* inherit size from .custom-dropdown */
    padding: .5em; /* add some space*/
    margin: 0; /* remove default margins */
}

.custom-dropdown__select--white {
    background-color: #fff;
    color: #444;    
}

@supports (pointer-events: none) and
      ((-webkit-appearance: none) or
      (-moz-appearance: none) or
      (appearance: none)) {

    .custom-dropdown {
        position: relative;
        display: inline-block;
        vertical-align: middle;
    }

    .custom-dropdown__select {
        padding-right: 2.5em; /* accommodate with the pseudo elements for the dropdown arrow */
        border: 0;
        border-radius: 3px;
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;    
    }

    .custom-dropdown::before,
    .custom-dropdown::after {
        content: "";
        position: absolute;
        pointer-events: none;
    }

    .custom-dropdown::after { /*  Custom dropdown arrow */
        content: "\25BC";
        height: 1em;
        font-size: .625em;
        line-height: 1;
        right: 1.2em;
        top: 50%; margin-top: -.5em;
    }

    .custom-dropdown::before { /*  Custom dropdown arrow cover */
        width: 2em;
        right: 0; top: 0; bottom: 0;
        border-radius: 0 3px 3px 0;
    }

    .custom-dropdown__select[disabled] {
        color: rgba(0,0,0,.3);
    }

    .custom-dropdown.custom-dropdown--disabled::after {
        color: rgba(0,0,0,.1);
    }

    /* White dropdown style */
    .custom-dropdown--white::before {
        top: .5em; bottom: .5em;
        background-color: #fff;
        border-left: 1px solid rgba(0,0,0,.1);
    }

    .custom-dropdown--white::after {
        color: rgba(0,0,0,.9);
    }

    /* FF only temp fix */
    @-moz-document url-prefix() {
        .custom-dropdown__select              { padding-right: .9em }
        .custom-dropdown--large .custom-dropdown__select { padding-right: 1.3em }
        .custom-dropdown--small .custom-dropdown__select { padding-right: .5em }
    }
}

IV. Les explications

Au premier abord, le CSS peut sembler un peu imposant, alors décomposons-le.

IV-A. appearance

La déclaration appearance: none est utilisée pour réinitialiser le rendu par défaut de la liste. La propriété appearance est très utile lorsque vous voulez donner à un élément un rendu spécifique qu'il ne possède pas par défaut ou si, comme dans notre cas, vous souhaitez supprimer complètement son apparence par défaut.

Vous vous demandez peut-être à quoi correspond la déclaration @-moz-document url-prefix() dans le CSS. Il s'agit d'une astuce pour cibler uniquement les navigateurs Firefox. Elle permet de corriger un ancien bogue de Firefox concernant le fonctionnement de appearance: none. En résumé, même si la propriété semble fonctionner, la flèche native de la liste déroulante s'affiche quand même.

Ainsi, une solution temporaire est de masquer la flèche en la décalant :

 
Sélectionnez
@-moz-document url-prefix() {
  ...
}

IV-B. ::before et ::after

Après avoir appliqué la déclaration ci-dessus et réinitialisé l'apparence de la liste déroulante, nous allons mettre en place notre propre flèche en utilisant des pseudoéléments. Il n'y a pas grand-chose à rajouter là-dessus : les pseudoéléments sont maintenant connus et répandus, on en voit partout.

IV-C. pointer-events

Si vous ne connaissez pas les pointer-events, sachez qu'avec la déclaration pointer-events: none, qui est la plus utilisée, vous pouvez annuler les événements liés à la souris par exemple pour la navigation sur la page. Dans notre cas, nous supprimons les événements de souris sur le triangle de droite pour éviter des problèmes d'accessibilité.

Il y a un effet indésiré malgré tout. Si on applique pointer-events: none à un élément, il n'est plus possible d'intercepter (et de styliser) son état de survol.

IV-D. @supports

Pour finir, mais pas des moindres, la propriété @supports permet la détection de fonctionnalités. Détecter le support d'une fonctionnalité en JavaScript n'est pas nouveau, Modernizr en est le meilleur exemple. Donc si vous connaissez Modernizr, vous pouvez considérer que @supports est son équivalent en CSS.

Pour éviter une mauvaise interprétation par les navigateurs, la règle suivante ne cible que ceux supportant les propriétés pointer-events et appearance. Les meilleurs exemples dans ce cas sont IE9 et IE10 qui supportent tous deux de nombreuses fonctionnalités CSS3 dans le code ci-dessus, mais pas encore les récents pointer-events ou appearance, ce qui rend le code inopérant.

 
Sélectionnez
@supports (pointer-events: none) and
    ((-webkit-appearance: none) or
     (-moz-appearance: none) or
     (appearance: none)) {
   ...
}

V. Un peu de JavaScript

La vérité est qu'il est possible de parler de technique uniquement CSS pour l'application des styles, mais pour reproduire aussi le cas où la liste déroulante est disabled, vous aurez besoin d'un peu de JavaScript pour cibler l'élément parent et lui appliquer une classe HTML comme custom-dropdown--disabled.

 
Sélectionnez
(function(){
    /*1*/var customSelects = document.querySelectorAll(".custom-dropdown__select");
    /*2*/for(var i=0; i<customSelects.length; i++){
        if (customSelects[i].hasAttribute("disabled")){
            customSelects[i].parentNode.className += " custom-dropdown--disabled";
        }
    }
})()
  • Retourne l'ensemble des éléments du document ayant la classe .custom-dropdown__select.
  • Pour chacun d'eux, retrouve son élément parent et lui applique la classe custom-dropdown--disabled. De cette façon, nous pourrons utiliser CSS pour customiser la flèche des listes désactivées.

Bien sûr, rien de tout cela ne serait nécessaire si l'élément <select>, qui est un élément remplacé, pouvait recevoir des pseudoéléments ou s'il existait un sélecteur CSS d'élément parent. Mais c'est une autre histoire.

VI. Support des navigateurs

D'après mes tests, le code fonctionne sur Firefox, Chrome et Opera Next. Mais vous devriez le voir fonctionner rapidement sur les prochaines versions des navigateurs (surtout au rythme où sortent les versions). Vérifiez les tables de compatibilité pour savoir où en est le support.

Voir une démo en ligne.

Remerciements

Cet article a été publié avec l'aimable autorisation de Catalin Rosu. L'article original (Making HTML dropdowns not suck) peut être vu sur le site Red Team Design.

Nous tenons à remercier ClaudeLELOUP pour sa relecture attentive de cet article.