Créer une barre de progression stylisée

L'élément <progress> est le moyen sémantique de représenter la progression d'une tâche, comme le téléchargement ou l'importation d'informations, ou l'achèvement d'une partie d'un processus en plusieurs étapes. L'utilisation de l'élément natif facilite l'accessibilité, car les lecteurs d'écran ont un role="progressbar" implicite et peuvent annoncer des valeurs.

Ce guide implémente une barre de progression horizontale entièrement stylisée avec des personnalisations de style.

Barre ou cercle de progression ?

  • Si l'espace est limité ou si un format carré est préférable, envisagez d'utiliser une barre de progression circulaire (cercle de progression) : Cercle de progression
  • Privilégiez les icônes de chargement pour la plupart des scénarios de progression indéterminée (Icône de chargement). Utilisez des barres de progression indéterminées lorsque l'état indéterminé est temporaire et sera bientôt remplacé par une barre de progression déterminée (par exemple, en attendant les métadonnées d'un import avant de pouvoir afficher un pourcentage de progression).

Annoter

  1. Utilisez un élément <progress> natif : il offre une accessibilité intégrée et un comportement cohérent avec la plate-forme.
  2. Utilisez max si vous avez besoin d'une échelle différente de celle par défaut (0-1).
  3. Pour une valeur de progression spécifique, définissez un attribut value. Omettez value pour un état indéterminé (par exemple, avant que des commentaires plus concrets ne commencent).
  4. Utilisez <label for="...">, aria-labelledby ou aria-label pour associer l'élément de progression à un libellé.

De quel type de personnalisation avez-vous besoin ?

Pour un style limité, ces propriétés sont suffisantes :

  • accent-color pour modifier la couleur de la barre de progression
  • width / inline-size pour modifier la largeur de la barre de progression
  • height/block-size s'applique de manière incohérente : testez d'abord
  • writing-mode: vertical-lr pour les barres verticales.
  • Effets qui s'appliquent après l'affichage : opacity, filter, clip-path, masques, transformations, box-shadow, etc.

Si un style limité suffit, ignorez la section suivante.

AVERTISSEMENT : Certaines propriétés (par exemple, border-radius, background, border) désactiveront silencieusement le style de la barre de progression par défaut. Si vous avez besoin d'un style plus complexe que celui proposé par les propriétés ci-dessus, vous devez implémenter un style entièrement personnalisé, comme décrit dans la section suivante. N'appliquez PAS de style plus étendu "au cas où cela fonctionnerait".

Style entièrement personnalisé

1. Configuration de base

  • OBLIGATOIRE : Utilisez appearance: none pour activer le style personnalisé.
  • Réinitialiser les bordures UA avec border: 0 solid
  • Pour limiter la barre active au rayon de bordure de l'élément, utilisez overflow: clip OU appliquez le même border-radius à la barre. N'utilisez PAS border-radius: inherit sur la barre, car cela dépend de la structure spécifique des éléments du Shadow DOM.
progress {
  /* Reset default browser styles */
  appearance: none;
  border: 0 solid;
  /* Clip bar to element’s border-radius  */
  overflow: clip;
}

2. Attribution d'un style

  • Appliquez un style à la piste sur l'élément progress lui-même.
  • Utilisez le pseudo-élément approprié ciblant le navigateur pour cibler la barre de progression elle-même : ::-webkit-progress-value pour WebKit/Blink et ::-moz-progress-bar pour Firefox.
  • OBLIGATOIRE : Ne regroupez PAS les pseudo-éléments -webkit- et -moz- dans la même règle. La règle sera considérée comme non valide et sera ignorée. :is() et :where() ne sont pas utiles ici, car ils ne sont pas compatibles avec les pseudo-éléments.
  • Chaque pseudo-élément ciblé par le navigateur doit apparaître exactement une fois. Les personnalisations spécifiques à l'état (telles que la taille et l'animation du segment indéterminé) doivent être acheminées via des propriétés personnalisées définies sur l'élément progress lui-même.
  • Facultatif : ajoutez une transition inline-size sur le remplissage pour que les changements de valeur déterminés soient animés au lieu de sauter. Firefox dimensionne le remplissage dans la mise en page et l'ignore, ce qui est sans danger.
progress {
  --progress-bar-radius: 1e5px;
  border-radius: var(--progress-bar-radius);
  /* Apply color for custom progress track (background) */
  background: var(--progress-bar-track, var(--color-neutral-90));
  /* Subtle depth on the empty part of the track */
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
}

/* Let <progress> styling show through */
progress::-webkit-progress-bar {
  background: transparent;
}

/* Webkit, Chrome */
progress::-webkit-progress-value {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

/* Firefox */
progress::-moz-progress-bar {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

3. Style de l'état indéterminé

Ajoutez une animation personnalisée pour l'état indéterminé : transformez le pseudo-élément de remplissage en un segment court et faites-le glisser le long de la piste.

  • Appliquez un style au remplissage lui-même. Ne le recréez PAS en tant qu'arrière-plan sur progress.
  • Le remplissage indéterminé n'existe que par son animation : les images clés définissent sa longueur avec un inline-size réel (qui remplace le inline-size intégré de l'UA de Chromium) et le déplacent avec translate. N'utilisez PAS scale pour la longueur, car cela déforme tout motif peint sur le remplissage.
  • Les pourcentages translate sont résolus par rapport à la largeur du remplissage. Exprimez donc les positions en unités de piste : faites de progress un conteneur de taille intégrée et utilisez cqi dans les images clés. Incluez margin-right: auto dans from, to pour que Chromium place le remplissage à gauche physiquement, même en mode de lecture de droite à gauche.
  • Utilisez des transformations, et non des marges, pour déplacer le remplissage.
    • Les transformations conservent l'animation dans le compositeur (sauf si inline-size est également animé).
    • Les marges sont rompues en RTL pour deux raisons distinctes :
    • Firefox positionne le remplissage indéterminé en utilisant uniquement sa marge physique gauche. Par conséquent, margin-inline-start n'a aucun effet en mode RTL et le remplissage ne se déplace jamais.
    • Dans Chromium et WebKit, qui disposent le remplissage comme un bloc normal, une fois qu'une marge pousse le remplissage au-delà de la fin de la piste, les marges s'additionnent pour dépasser la piste. Le CSS standard résout ce problème en ignorant margin-left dans les langues se lisant de droite à gauche. Le segment passe donc au bord au lieu de glisser.
    • translate est appliqué après la mise en page, donc aucun de ces problèmes ne s'applique.
  • Pour mettre en miroir le mouvement de droite à gauche (comme le fait la barre native de Chrome), inversez l'animation sous :dir(rtl) :
progress:dir(rtl) {
  --progress-bar-indeterminate-animation: progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear reverse;
}
  • Définissez les propriétés --progress-bar-indeterminate-* publiques sur progress (leur nom les limite déjà à l'état) afin que les consommateurs puissent les remplacer sans connaître :indeterminate.
  • Pour faire varier la longueur du segment (par exemple, le bord d'attaque avance et le bord de fuite rattrape son retard), définissez --progress-bar-indeterminate-animation sur deux animations simultanées : une dont les images clés animent translate dans cqi (position) et une dont les images clés animent inline-size (longueur), de sorte que chacune ait ses propres images clés et son propre lissage de vitesse. N'essayez PAS de mettre en forme les deux bords dans une seule liste d'images clés. La propriété animation-timing-function par image clé s'applique à toutes les propriétés de cette image clé.
  • --progress-bar-animation s'applique dans les deux états et peut être utilisé pour des effets tels qu'un scintillement déterminé. revert-rule n'est utilisé que pour remplacer lorsqu'il est défini, afin de maximiser la composabilité.
  • Ne désactivez jamais l'animation indéterminée (animation: none laisse un remplissage de largeur nulle dans Chromium). Pour réduire les mouvements, ralentissez-la plutôt.
progress {
  /* Lets the fill position itself in track units (cqi) */
  container-type: inline-size;
  /* Segment length relative to the track */
  --progress-bar-indeterminate-size: 30%;
  --progress-bar-indeterminate-animation-duration: 1.5s;

  /* Respect prefers-reduced-motion */
  @media (prefers-reduced-motion: reduce) {
    --progress-bar-indeterminate-animation-duration: 10s;
  }
}

progress:indeterminate {
  --progress-bar-animation: var(
      --progress-bar-indeterminate-animation,
      progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear
    );
}

/* Add to the existing pseudo-element rules */
progress::-webkit-progress-value {
  animation: var(--progress-bar-animation, revert-rule);
}

progress::-moz-progress-bar {
  animation: var(--progress-bar-animation, revert-rule);
}

/* Physical: left to right regardless of direction (reverse under :dir(rtl) to mirror). */
@keyframes progress-bar-indeterminate {
  from, to {
    inline-size: var(--progress-bar-indeterminate-size);
    /* Pin to the physical left; Chromium would otherwise lay the fill out at the inline start */
    margin-right: auto;
  }
  from { translate: -100%; } /* Own width: fully outside the left edge */
  to { translate: 100cqi; }  /* Track width: fully outside the right edge */
}

Autres considérations sur l'accessibilité

En plus des consignes mentionnées ci-dessus :

  • Annoncez la fin de l'opération sans voler le curseur : ne définissez pas tabindex et n'appelez pas .focus() sur <progress>, car il n'est pas interactif et le déplacement du curseur vers celui-ci perturbe les utilisateurs du clavier. Pour annoncer la fin d'une tâche en arrière-plan, mettez à jour un élément d'état poli (role="status" ou aria-live="polite").
  • N'ajoutez pas de texte de remplacement dans <progress> : le texte dans <progress> est ignoré par les navigateurs récents et n'est pas utilisé par les technologies d'assistance.
  • Évitez d'utiliser aria-busy + aria-describedby sur un <div> générique sans libellé. La compatibilité de aria-busy avec les lecteurs d'écran est irrégulière, et aria-describedby sur un élément <div> sans nom et sans rôle est ignoré par les technologies d'assistance. Si le contenu d'une région n'est pas prêt lors du chargement, masquez-le avec hidden ou aria-hidden="true".
  • À FAIRE : Assurez-vous que le contraste entre la barre de progression et la piste est suffisant lorsque vous ajoutez des styles personnalisés.
  • Envisagez d'autres solutions pour représenter un état indéterminé : les animations de barre de progression indéterminée affichées en parallèle d'autres contenus peuvent être considérées comme une distraction par certains utilisateurs.

Stratégies de remplacement

Solutions de remplacement et compatibilité des navigateurs pour accent-color

La propriété accent-color est une amélioration progressive. Les navigateurs qui ne sont pas compatibles avec cette propriété l'ignoreront et utiliseront les couleurs par défaut de leur UI.

Si accent-color n'est pas compatible et que vous devez appliquer un style à la barre de progression, utilisez la méthode de style personnalisé pour appliquer le style de votre choix à la barre de progression.

revert-rule : solutions de remplacement et compatibilité avec les navigateurs

Si revert-rule n'est pas compatible, animation: var(--progress-bar-animation, revert-rule) utilise animation-name: revert-rule avec une durée de 0s lorsque --progress-bar-animation n'est pas défini, ce qui est une opération sans effet et sans danger lorsqu'aucun @keyframes revert-rule n'existe.