Les exemples suivent le métier, la densité et le thème choisis.

Listes et contenu

Glisser-déposer

nouveauClasses hb-handle hb-drag-ghost hb-drag-lifted hb-drop-over hb-list-handle hb-list-row--ghostVuetify 2 vuedraggable 2 (Sortable.js) : handle=".hb-handle", ghost-class="hb-drag-ghost", drag-class="hb-drag-lifted", force-fallback, :animation="180"Vuetify 3 vuedraggable@next ou vue-draggable-plus, mêmes options

Quand l'utiliser

Pour changer l'ordre d'une liste courte quand cet ordre a un sens pour l'utilisateur : tâches d'une check-list, étapes d'une procédure, rayons affichés sur la tablette, colonnes d'un export. Pour classer selon une valeur (date, prix), un tri de tableau. Pour envoyer un fichier, la zone de dépôt.

États

Au repos, une poignée drag_indicator à gauche. Pendant le déplacement, la ligne tenue se soulève (ombre de niveau 3, légèrement agrandie) et suit le pointeur ; l'emplacement où elle arrivera est dessiné en pointillé, en couleur métier ; les autres lignes s'écartent en 180 ms. Ici, « Nettoyer la vitrine » est en train de descendre en bas de la liste.

Relever les températures
Vérifier les DLC
Nettoyer la vitrine
Nettoyer la vitrine
Code
<div style="max-width:560px">
  <div class="hb-list">
    <div class="hb-list-row"><button class="hb-list-handle" aria-label="Déplacer « Relever les températures »"><i class="material-icons">drag_indicator</i></button><input type="checkbox" class="hb-check hb-list-check" aria-label="Faite"><div class="hb-list-body"><span class="hb-list-title">Relever les températures</span></div></div>
    <div class="hb-list-row"><button class="hb-list-handle" aria-label="Déplacer « Vérifier les DLC »"><i class="material-icons">drag_indicator</i></button><input type="checkbox" class="hb-check hb-list-check" aria-label="Faite"><div class="hb-list-body"><span class="hb-list-title">Vérifier les DLC</span></div></div>
    <div class="hb-list-row hb-list-row--ghost"><span class="hb-list-handle"></span><span></span><div class="hb-list-body"><span class="hb-list-title">Nettoyer la vitrine</span></div></div>
  </div>
  <div class="hb-list-row hb-drag-lifted" style="position:relative;margin:calc(-.6 * var(--row)) -12px 0 24px"><button class="hb-list-handle" aria-label="Déplacer « Nettoyer la vitrine »"><i class="material-icons">drag_indicator</i></button><input type="checkbox" class="hb-check hb-list-check" aria-label="Faite"><div class="hb-list-body"><span class="hb-list-title">Nettoyer la vitrine</span></div></div>
</div>
ClasseRôleOption Sortable.js
hb-handlepoignée seule, à poser dans n'importe quelle ligne (liste simple, ligne de tableau, carte)handle
hb-list-handlela poignée de la liste de tâches, révélée au survolhandle
hb-drag-ghost (hb-list-row--ghost)l'emplacement d'arrivée : pointillé et fond doux métier, contenu masquéghostClass
hb-drag-liftedla copie qui suit le pointeur : ombre de niveau 3, agrandie de 2 %dragClass
hb-drop-overune autre liste ou un onglet survolé, qui recevra la ligneajoutée par l'application (@move)

Vers une autre liste

Quand une ligne peut changer de groupe (une tâche vers une autre check-list, une commande vers un autre jour), la liste ou l'onglet qui la recevra prend hb-drop-over.

Aujourd'hui

Commander le saumon

Demain

Inventaire du rayon
Vérifier les DLC
Code
<div class="hb-row" style="align-items:flex-start;gap:var(--space-4)">
  <div style="flex:1;min-width:220px"><p class="hb-label">Aujourd'hui</p><div class="hb-list"><div class="hb-list-row"><button class="hb-list-handle" aria-label="Déplacer « Commander le saumon »"><i class="material-icons">drag_indicator</i></button><input type="checkbox" class="hb-check hb-list-check" aria-label="Faite"><div class="hb-list-body"><span class="hb-list-title">Commander le saumon</span></div></div></div></div>
  <div style="flex:1;min-width:220px"><p class="hb-label">Demain</p><div class="hb-list hb-drop-over"><div class="hb-list-row"><button class="hb-list-handle" aria-label="Déplacer « Inventaire du rayon »"><i class="material-icons">drag_indicator</i></button><input type="checkbox" class="hb-check hb-list-check" aria-label="Faite"><div class="hb-list-body"><span class="hb-list-title">Inventaire du rayon</span></div></div><div class="hb-list-row hb-list-row--ghost"><span class="hb-list-handle"></span><span></span><div class="hb-list-body"><span class="hb-list-title">Vérifier les DLC</span></div></div></div></div>
</div>

Sans glisser

Glisser demande un geste précis : au doigt, avec des gants, au clavier ou avec un lecteur d'écran, il ne suffit pas. Toute liste qui se réordonne propose donc aussi :

  • au clavier : la poignée est un <button> ; Flèche haut et Flèche bas déplacent la ligne d'un cran, le focus reste sur la poignée ;
  • au pointeur, sans glisser : « Monter », « Descendre », « Déplacer en haut », « Déplacer en bas » dans le menu ⋮ de la ligne (et « Déplacer vers… » quand elle peut changer de liste).
arrow_upwardMonter
arrow_downwardDescendre
vertical_align_topDéplacer en haut
vertical_align_bottomDéplacer en bas
Code
<div class="hb-menu" style="width:220px">
  <div class="hb-menu-item"><i class="material-icons" style="font-size:18px">arrow_upward</i>Monter</div>
  <div class="hb-menu-item"><i class="material-icons" style="font-size:18px">arrow_downward</i>Descendre</div>
  <div class="hb-menu-sep"></div>
  <div class="hb-menu-item"><i class="material-icons" style="font-size:18px">vertical_align_top</i>Déplacer en haut</div>
  <div class="hb-menu-item"><i class="material-icons" style="font-size:18px">vertical_align_bottom</i>Déplacer en bas</div>
</div>

Après chaque déplacement, une phrase est lue aux lecteurs d'écran dans une zone role="status" masquée (hb-sr-only) : « Nettoyer la vitrine, position 2 sur 4 ».

Règles

  • On saisit par la poignée, jamais par toute la ligne : la ligne garde le clic pour éditer, la case pour cocher, et le doigt pour faire défiler. La poignée est toujours à gauche.
  • Au doigt, la poignée est toujours visible et sa zone de prise fait 44 px, sans changer la mise en page. Elle bloque le défilement (touch-action: none) : on n'a pas besoin d'appui long.
  • La liste défile seule quand la ligne approche du bord de l'écran ou de la carte.
  • Le lâcher enregistre tout de suite, sans bouton « Enregistrer ». Si l'enregistrement échoue, la ligne revient à sa place et un toast d'erreur le dit.
  • Une liste triée par une valeur (date, nom) ne se réordonne pas à la main : on n'affiche pas de poignée.
  • Avec prefers-reduced-motion, les lignes changent de place sans glisser (animation: 0).

Dans Vuetify

<draggable v-model="tasks" item-key="_id" handle=".hb-list-handle"
  ghost-class="hb-list-row--ghost" drag-class="hb-drag-lifted"
  :force-fallback="true" :animation="calm ? 0 : Hublot.motion.state"
  @end="saveOrder">

force-fallback remplace l'image fantôme du navigateur (différente sur chaque système, absente sur iOS) par une vraie copie de la ligne, habillée par hb-drag-lifted. calm vaut matchMedia('(prefers-reduced-motion: reduce)').matches.