aller au contenu principal

Personnaliser l'apparence d'un input de type file en CSS

Avec même un peu de JS !


Publié le 08/11/2023 dans BlogMis à jour le 10/02/2024
Crédit photo - Unsplash - Ruvim NogaCrédit photo : Unsplash - Ruvim Noga
Je suis en train d'écrire un cours complet sur Flexbox. Il y aura un module entier sur la compréhension de ce mode de layout, et un module 100% pratique avec plus d'une dizaine d'exercices.Tu peux consulter le programme et à te préinscrire dès maintenant ! ⬇️Se préinscrire

⚠️ Avant de commencer, je tiens à préciser que la personnalisation des éléments natifs du navigateur est à pratiquer avec prudence. Je te recommande donc de bien tester sur les différents navigateurs afin de garantir une bonne expérience utilisateur et une bonne accessibilité.

Contrairement à ce que l'on pourrait penser, le CSS permet d'atteindre un très bon niveau de personnalisation des éléments de formulaire.

Je te propose de découvrir dans ce guide comment personnaliser l'apparence un input de type file sans le dissimuler.

Nous verrons aussi comment créer une dropzone en CSS et avec du JS.


L'apparence par défaut

Intéressons en premier à l'apparence par défaut d'un input de type file.

Le code HTML est très simple :

<input type="file" />

Voici le résultat :

Ce n'est pas très beau n'est-ce pas ? 😅

Aussi, l'apparence d'un input de type file varie d'un navigateur à l'autre, mais ce n'est pas un problème, ce qui compte c'est qu'il soit fonctionnel :

L'élément input de type file avec son apparence par défaut sur les différents navigateurs

On ne pourra cependant pas agir sur les textes affichés par défaut, comme celui du bouton, et celui du placeholder.

Mais c'est une bonne chose, laissons le navigateur gérer la traduction de ces textes en fonction de la langue du navigateur.

Pour le reste, voyons ce que l'on peut faire pour améliorer le design de l'input 🙂

Styliser l'input en le cachant ? Non ! 😡

C'est une façon encore très répandue de styliser un input de type file.

En tant que défenseur du HTML natif, et de l'accessibilité, je ne recommande pas cette technique.

Mais pour certains design, c'est parfois la seule solution.

Nous n'allons pas voir cette technique dans ce guide, mais je te propose de t'expliquer comment elle fonctionne.

L'idée est de cacher l'élément input et de le remplacer par un autre élément créé de toute pièce en HTML, cet élément est placé dans le label qui possède le même id que l'input.

<label for="images">
  <span>Choisir une image</span>
  <input type="file" id="images" />
</label>
/* On cache l'input 😢 */
input[type=file] {
  display: none;
}

/* On créé un bouton en ciblant un élément dans la balise label */
label span {
  padding: 8px 12px;
  background: #084cdf;
  color: #fff;
  cursor: pointer;
}

label span:hover {
  background: #0d45a5;
}

Si tu cliques sur le bouton, tu verras que l'input est bien cliqué, et que le fichier est bien sélectionné, mais tu ne peux pas voir le nom de ton fichier, ou le nombre de fichier.

Donc même si l'ajout de fichier fonctionne, on perd une fontionnalité importante.

Cette technique présente néanmoins un avantage, on peut créer l'élément de notre choix, ajouter des icônes etc.

Mais est-ce qu'on a vraiment besoin de cela ? Pas forcément.

Styliser l'élément input en CSS ? Oui ! 🤗

Ce que l'on peut faire, c'est cibler l'input en CSS et lui donner une nouvelle apparence.

Ajoutons des padding, une bordure, un background, un border-radius, et changeons la couleur de texte. non pas du bouton, mais le texte affiché par défaut si aucun fichier n'est sélectionné. C'est aussi le texte qui sera affiché après la sélection d'un fichier.

input[type=file] {
  color: #222245;
  padding: 8px 12px;
  background-color: #fff;
  border: 1px solid #222245;
}

Le résultat :

Ok c'est pas mal tout ca, mais on ne peut pas styliser le bouton si ?

Styliser le bouton de l'input ? C'est possible ?

Oui c'est possible, merci au pseudo-élément ::file-selector-button qui permet de styliser le bouton d'un input file.

Une bonne raison de ne plus cacher l'input pour faire un simple bouton !

Le bouton d'un input file fait partie du shadow DOM, le pseudo-élément est donc un moyen d'y accéder.

input[type=file]::file-selector-button {
  margin-right: 8px;
  border: none;
  background: #084cdf;
  padding: 8px 12px;
  color: #fff;
  cursor: pointer;
}

Le résultat :

Ajouter des effets au hover

Le bouton d'un input file est un bouton presque comme un autre, on peut donc lui ajouter des effets au hover.

input[type=file]::file-selector-button:hover {
  background: #0d45a5;
}

Pas d'effet au focus ? 🤔

Il n'est pas nécessaire de jouer sur le focus du bouton, car l'input file est un élément de formulaire, et donc il peut déjà recevoir un focus.

input[type=file]:focus {
  outline: 5px dashed #000;
  outline-offset: 2px;
}

Lorsqu'il reçoit le focus, l'input serait comme ceci :

Adapter l'input pour un dark mode

Pour la gestion des light et dark mode, on peut utiliser la media query prefers-color-scheme qui permet de mettre des règles en fonction des préférences d'affichage de l'utilisateur.

On peut considérer que la version light est la version par défaut, et que la version dark est la version adaptative.

input[type=file] {
  color: #222245;
  padding: 8px;
  background-color: #fff;
  border: 1px solid #222245;
}

input[type=file]:focus {
  outline: 2px dashed #222245;
  outline-offset: 2px;
}

input[type=file]::file-selector-button {
  margin-right: 8px;
  border: none;
  background: #222245;
  padding: 8px 12px;
  color: #fff;
  cursor: pointer;
}

input[type=file]::file-selector-button:hover {
  background: #4747b8;
}

/* Les styles du dark mode */
@media (prefers-color-scheme: dark) {
  input[type=file] {
    color: #fff;
    border: 1px solid #35356e;
    background-color: #1e1e3f;
  }

  input[type=file]:focus {
    outline: 2px dashed #7c7cc0;
    outline-offset: 2px;
  }

  input[type=file]::file-selector-button {
    background: #35356e;
    color: #fff;
  }
}

Si on regarde le résultat sur les différents navigateurs, il ne restera que des micros différences comme la hauteur et largeur de l'input, ainsi que la taille du texte.

Mais je ne préfère pas y toucher, laissons le navigateur gérer cela.

Cependant notre personnalisation a amené un petit problème, observable sur Firefox et Edge, le texte du placeholder est légèrement tronqué :

L'élément input de type file une fois stylisé sur les différents navigateurs, avec le texte du placeholder tronqué sur Firefox et Edge

La raison : le padding de l'input et la taille de la bordure n'ont pas été ajoutés au calcul de la largeur de l'input.

L'input a gardé sa largeur native.

Ce n'est pas très grave et le problème peut être réglé en forcant une width, par exemple de 350px, et en lui passant une max-width de 100% pour qu'il s'adapte à la largeur de son parent :

input[type=file] {
  width: 350px;
  max-width: 100%;
}

Voici le code complet, le thème de l'input s'adapte en fonction des préférences d'affichage de ton OS.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="/styles.css" />
  </head>

  <body>
    <input type="file" />
  </body>

</html>

Passons maintenant à la création d'une dropzone ! 🤩

Créer une dropzone en CSS et en JS

Allons plus loin et créons une dropzone. Une dropzone est une zone dans laquelle on peut déposer (avec du drag'n drop) des fichiers.

Ici aussi, le CSS peut être utilisé pour créer la dropzone, mais il faut aussi ajouter un peu de JavaScript pour gérer les évènements de drag'n drop.

C'est la détection du clic sur la dropzone qui peut être gérée par le CSS, en utilisant un label.

Pour cela, il faut que la valeur de l'attribut for du label soit la même que l'identifiant de l'input.

<label for="images" class="drop-container" id="dropcontainer">
  <span class="drop-title">Déposer des images ici</span>
  ou
  <input type="file" id="images" accept="image/*" required />
</label>
.drop-container {
  position: relative;
  display: flex;
  gap: 10px;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 200px;
  padding: 20px;
  border-radius: 10px;
  border: 2px dashed #555;
  color: #444;
  cursor: pointer;
  transition: background .2s ease-in-out, border .2s ease-in-out;
}

.drop-container:hover {
  background: #eee;
  border-color: #111;
}

La dropzone est créée, et le clic est bien détecté grace au label.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="/styles.css" />
  </head>
  
  <body>
    <label for="images" class="drop-container" id="dropcontainer">
      <span class="drop-title">Déposer vos fichiers</span>
      ou
      <input type="file" id="images" accept="image/*" required>
    </label>

  </body>

</html>

Ajouter du JavaScript pour la gestion des évènements

Pour la gestion des évènements de drag'n drop, on va utiliser les évènements dragover, dragenter, dragleave et drop :

  • dragover : l'évènement est déclenché lorsque l'élément est survolé par un élément draggable (une image par exemple)
  • dragenter : l'évènement est déclenché lorsque l'élément draggable entre dans la zone de drop
  • dragleave : l'évènement est déclenché lorsque l'élément draggable quitte la zone de drop
  • drop : l'évènement est déclenché lorsque l'élément draggable est déposé dans la zone de drop
const dropContainer = document.getElementById("dropcontainer");
const fileInput = document.getElementById("images");

dropContainer.addEventListener("dragover", (e) => {
  e.preventDefault()
}, false);

dropContainer.addEventListener("dragenter", () => {
  dropContainer.classList.add("drag-active")
});

dropContainer.addEventListener("dragleave", () => {
  dropContainer.classList.remove("drag-active")
});

dropContainer.addEventListener("drop", (e) => {
  e.preventDefault()
  dropContainer.classList.remove("drag-active")
  fileInput.files = e.dataTransfer.files
});

Pourquoi mettre un e.preventDefault() sur les évènements dragover et drop ? 🤔

C'est pour éviter que le navigateur ouvre le fichier dans un nouvel onglet.

C'est le comportement par défaut du navigateur, on peut le garder sur le reste de la page, mais pas sur la dropzone.

Pour récupérer le ou les fichiers déposés dans la dropzone, on utilise la propriété files de l'input, et on lui affecte la valeur de e.dataTransfer.files.

On termine par ajouter les styles pour la dropzone active :

.drop-container.drag-active {
  background-color: #282853;
}

Tu trouveras ici le code complet de la dropzone :

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="/styles.css" />
  </head>
  
  <body>
    <label for="images" class="drop-container" id="dropcontainer">
      <span class="drop-title">Drop files here</span>
      or
      <input type="file" id="images" accept="image/*" required>
    </label>

    <script src="/script.js"></script>
  </body>

</html>

Je te remercie de m'avoir lu, n'hésites pas à me donner ton avis sur Twitter !

Si tu souhaites être prévenu de la sortie des prochains articles et contenu du site, tu trouveras le formulaire d'inscription à ma newsletter un peu plus bas.

À bientôt, Seb 🤘

2024 Sébastien Imbert