Il y a quelques temps de cela, lorsque je faisais des recherches sur les boutons radios et checkboxes personnalisables en CSS, j’avais en tête de faire la même chose avec le champ « fichier » (input-file). J’avais donc fait un essai en CSS pur. Puis j’avais laissé l’idée de côté.

Finalement je suis revenu dessus hier pour compléter l’idée de base par une information visuelle textuelle. En effet si vous essayez cette version de l’input-file personnalisé en CSS, vous verrez que rien ne vous indique qu’une image est sélectionnée par vos soins. Ce qui est plutôt gênant pour un utilisateur.

J’ai alors cherché à le faire en JS (vanilla, pas de jQuery), et ce n’est pas si complexe que cela.

Démonstration

Le code HTML

Pour rendre la chose la plus « naturelle » possible, nous allons nous contenter d’un élément label et d’un input:file.
Le label sera utilisé comme élément « sensible », comme un bouton, pour activer le champ de sélection d’un fichier.

<div class="input-file-container">
  <input class="input-file" id="my-file" type="file">
  <label for="my-file" class="input-file-trigger" tabindex="0">Select a file...</label>
</div>
<p class="file-return"></p>

Je renseigne ici volontairement plusieurs classes pour manipuler les éléments en CSS et JS sans dépendre des éléments HTML.
tabindex="0" nous permet de rendre le label focusable notamment à la navigation clavier.

Quelques styles

Le conteneur va être positionné en relative afin de pouvoir placer l’input en position absolue.
On va donc se retrouver avec le label du champ servant de « déclencheur » (sensible au clic et touches du clavier) et le champ de recherche de fichiers par dessus mais transparent, servant lui aussi de déclencheur.
Nous allons faire en sorte de travailler la marge interne (padding) de ce champ afin de le superposer au label, rendant ainsi sensibles le label et le champ invisible sur une surface à peu près identique.

/* styles de base si JS est activé */
.js .input-file-container {
  position: relative;
  width: 225px;
}
.js .input-file-trigger {
  display: block;
  padding: 14px 45px;
  background: #39D2B4;
  color: #fff;
  font-size: 1em;
  transition: all .4s;
  cursor: pointer;
}
.js .input-file {
  position: absolute;
  top: 0; left: 0;
  width: 225px;
  padding: 14px 0;
  opacity: 0;
  cursor: pointer;
}
 
/* quelques styles d'interactions */
.js .input-file:hover + .input-file-trigger,
.js .input-file:focus + .input-file-trigger,
.js .input-file-trigger:hover,
.js .input-file-trigger:focus {
  background: #34495E;
  color: #39D2B4;
}
 
/* styles du retour visuel */
.file-return {
  margin: 0;
}
.file-return:not(:empty) {
  margin: 1em 0;
}
.js .file-return {
  font-style: italic;
  font-size: .9em;
  font-weight: bold;
}
/* on complète l'information d'un contenu textuel
   uniquement lorsque le paragraphe n'est pas vide */
.js .file-return:not(:empty):before {
  content: "Selected file: ";
  font-style: normal;
  font-weight: normal;
}

Pour le reste des choix graphiques (couleurs, typographie, etc) je vous laisse faire, c’est secondaire pour ce que je vous propose.

Retour visuel textuel grâce à JavaScript

Ce code est proposé en JS vanilla (c’est à dire sans Framework JS), il est possible de l’adapter à votre framework préféré si vous en utilisez un dans votre projet.

// ajout de la classe JS à HTML
document.querySelector("html").classList.add('js');
 
// initialisation des variables
var fileInput  = document.querySelector( ".input-file" ),  
    button     = document.querySelector( ".input-file-trigger" ),
    the_return = document.querySelector(".file-return");
 
// action lorsque la "barre d'espace" ou "Entrée" est pressée
button.addEventListener( "keydown", function( event ) {
    if ( event.keyCode == 13 || event.keyCode == 32 ) {
        fileInput.focus();
    }
});
 
// action lorsque le label est cliqué
button.addEventListener( "click", function( event ) {
   fileInput.focus();
   return false;
});
 
// affiche un retour visuel dès que input:file change
fileInput.addEventListener( "change", function( event ) {  
    the_return.innerHTML = this.value;  
});

Les actions sont relativement simples ici, j’espère que les commentaires vous suffiront 🙂

Le résultat et la présentation ici sont très sommaires, il vous appartiendra ensuite de ruser pour ajouter des petits effets visuels : une icône de fichier joint sur le bouton par exemple, ou une personnalisation du retour textuel suivant la langue, etc.

Démonstration Fork on CodePen.io