Créer une liste de cartes réordonnables par drag en React
Une liste de cartes réordonnables en React se construit avec Reorder.Group et Reorder.Item de Framer Motion. Le groupe gère l'état de l'ordre, chaque item reçoit une physique spring au drag (scale + ombre), et le relâché replace la carte dans sa nouvelle position automatiquement.
- Stack : React + Framer Motion (API Reorder) + Lucide React + Tailwind v4, ~100 lignes.
- API clé : Reorder.Group, Reorder.Item, whileDrag, useState pour le tableau ordonné.
- Config spring : stiffness 300, damping 25, repositionnement vif sans rebond excessif.
- Accessible : les cartes portent des h3 sémantiques ; le drag clavier n'est pas géré nativement par Reorder, les utilisateurs de lecteur d'écran ont besoin d'un mécanisme alternatif.
- Compatible tactile : le Reorder de Framer Motion fonctionne sur mobile via les pointer events.
Cette section bento transforme une liste de fonctionnalités statique en une expérience drag-to-reorder. Chaque carte se soulève avec un spring (scale + ombre) quand on la saisit, puis se replace dans son nouveau slot dès le relâché. Ce type de micro-interaction signale le soin apporté sur une landing produit ou un flux d'onboarding.
Anatomie
La section se découpe en trois couches. En haut, un bloc header optionnel (badge, h2, sous-titre) s'anime en fondu depuis le bas à l'entrée dans le viewport. En dessous, un Reorder.Group enveloppe une colonne flex verticale. Chaque Reorder.Item est une carte arrondie avec une poignée GripVertical à gauche, une icône Lucide colorée en accent, un titre gras et un paragraphe de description atténué. La grille reste en colonne unique avec un max-w-3xl pour un confort de lecture optimal.
Comment ça marche
Reorder.Group de Framer Motion prend une prop `values` (le tableau de l'ordre courant) et un callback `onReorder` qui le remplace par le nouveau tableau trié. Chaque Reorder.Item reçoit la `value` qu'il représente, une référence objet, pas un index. Pendant le drag, Framer Motion calcule les intersections avec les autres items et déclenche onReorder en temps réel, produisant une liste triée en direct sans manipulation manuelle d'indices. La prop `whileDrag` ajoute scale:1.03 et un box-shadow pendant le geste ; `transition` avec spring stiffness 300 / damping 25 gouverne l'atterrissage de la carte après relâché.
Comment le coder en React
Initialise l'état ordonné
Stocke ton tableau d'items dans un useState. Passe la valeur d'état à la prop `values` de Reorder.Group et le setter à `onReorder`. Le groupe gère le reste, pas de suivi d'index à faire de ton côté.
const [items, setItems] = React.useState(initialItems); <Reorder.Group axis="y" values={items} onReorder={setItems}> {items.map((item) => ( <DraggableCard key={item.id} item={item} /> ))} </Reorder.Group>Enveloppe chaque carte dans Reorder.Item
Passe l'objet item lui-même comme prop `value`. Framer Motion utilise l'égalité de référence pour identifier quel item a bougé, donc la value doit être la même référence objet que celle du tableau `values`. Ajoute `cursor-grab active:cursor-grabbing` pour une affordance claire.
<Reorder.Item value={item} className="rounded-2xl border p-6 cursor-grab active:cursor-grabbing" > {/* card content */} </Reorder.Item>Ajoute la physique spring au drag
Utilise `whileDrag` pour agrandir légèrement la carte et projeter une ombre. Le spring de `transition` sur l'item gouverne comment elle se replace quand on la lâche. Garde une stiffness élevée et un damping modéré pour que l'atterrissage soit rapide sans être brusque.
whileDrag={{ scale: 1.03, boxShadow: "0 10px 40px rgba(0,0,0,0.1)", }} transition={{ type: "spring", stiffness: 300, damping: 25 }}Résous les icônes Lucide par nom
Importe tout l'espace de noms Lucide et écris un petit helper qui retourne le composant icône par nom de chaîne. Stocke le nom de l'icône dans tes objets de données pour que le composant reste purement présentationnel sans import codé en dur par icône.
import * as LucideIcons from "lucide-react"; function getIcon(name?: string) { if (!name) return null; return (LucideIcons as unknown as Record<string, React.ElementType>)[name] ?? null; }
Quand l'utiliser
Ce pattern convient aux dashboards produit où les utilisateurs personnalisent l'ordre d'une liste de fonctionnalités, aux checklists d'onboarding où la séquence a du sens, ou aux landing pages où tu veux montrer de l'interactivité sans embarquer une bibliothèque drag-and-drop complète. À éviter sur les pages purement informatives où réordonner ne signifie rien, l'affordance crée une attente que l'ordre est persisté ou utilisé. Pense à sauvegarder le nouvel ordre en state ou en base quand l'interaction est fonctionnelle.
Utilisé par
- Linear, Le drag-to-reorder est central dans la gestion des priorités d'issues et de projets.
- Notion, Chaque bloc dans Notion est déplaçable via une poignée grip, la même affordance GripVertical utilisée ici.
- Trello, Le drag-and-drop de cartes entre colonnes est le modèle d'interaction principal du produit.
- Framer, Les panneaux de calques et les listes de composants de l'outil de design utilisent des interactions drag pilotées par spring.
FAQ
Le Reorder de Framer Motion fonctionne-t-il sur mobile/tactile ?
Oui. Framer Motion utilise les pointer events en interne, donc le toucher et le stylet déclenchent correctement le drag. Teste sur iOS Safari en particulier : le scroll à inertie peut entrer en conflit avec les gestes de drag vertical, il peut être nécessaire de bloquer le scroll par défaut sur le conteneur de liste.
Comment persister le nouvel ordre après que l'utilisateur a réordonné ?
Le callback `onReorder` reçoit le nouveau tableau à chaque mise à jour du drag. Pour la persistance, débounce un appel de sauvegarde dans un useEffect qui surveille l'état items, ou déclenche une requête POST sur un événement drag-end séparé via onDragEnd sur chaque Reorder.Item.
Peut-on utiliser ça avec une grille plutôt qu'une liste verticale ?
Reorder.Group supporte `axis="x"` pour les listes horizontales et `axis="y"` (utilisé ici) pour les verticales. Le réordonnancement en grille 2D n'est pas géré nativement par Reorder de Framer Motion ; pour ce cas, utilise une bibliothèque comme dnd-kit qui gère le drag multi-axe nativement.
L'interaction drag est-elle accessible aux utilisateurs clavier ?
Le Reorder de Framer Motion ne gère pas le réordonnancement clavier nativement. Pour une accessibilité complète, ajoute des boutons visibles (monter / descendre) qui effectuent des opérations splice sur le tableau items en complément de l'interface drag.