React + MotionBoutons

Bouton chargement et succès

Au clic, le texte laisse place à un spinner, puis à une coche de confirmation, avant de revenir à l’état initial.

Réglages

24px
1

Code

import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Check, Loader2 } from "lucide-react";

interface LoadingButtonProps {
  color?: string;
  radius?: number;
  text?: string;
  /** Multiplica la velocidad de la carga simulada. */
  intensity?: number;
}

export default function LoadingButton({
  color = "#0071e3",
  radius = 24,
  text = "Enregistrer",
  intensity = 1,
}: LoadingButtonProps) {
  const [state, setState] = useState<"idle" | "loading" | "success">("idle");

  return (
    <motion.button
      type="button"
      className="flex min-w-36 items-center justify-center gap-2 px-6 py-3 text-sm font-semibold text-white shadow-lg"
      style={{
        backgroundColor: state === "success" ? "#0071e3" : color,
        borderRadius: radius,
      }}
      animate={{ scale: state === "loading" ? 0.97 : 1 }}
      onClick={() => {
        if (state !== "idle") return;
        setState("loading");
        setTimeout(() => setState("success"), 1500 / intensity);
        setTimeout(() => setState("idle"), 1500 / intensity + 1800);
      }}
    >
      <AnimatePresence mode="wait" initial={false}>
        {state === "idle" && (
          <motion.span
            key="idle"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
          >
            {text}
          </motion.span>
        )}
        {state === "loading" && (
          <motion.span
            key="loading"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
          >
            <Loader2 className="h-4 w-4 animate-spin" />
          </motion.span>
        )}
        {state === "success" && (
          <motion.span
            key="success"
            initial={{ opacity: 0, scale: 0.5 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0 }}
            className="flex items-center gap-1.5"
          >
            <Check className="h-4 w-4" /> Terminé
          </motion.span>
        )}
      </AnimatePresence>
    </motion.button>
  );
}

Prompt pour l’IA

Collez-le dans ChatGPT, Claude ou Cursor : le bloc sera adapté à votre projet avec ces réglages, même sans React.

Plus dans Boutons

Où l’utiliser

Quand rien ne se passe, on clique une deuxième fois. Ce bouton répond tout de suite : il montre qu’il travaille, puis il confirme. Il convient au « Enregistrer » d’une page de réglages, à l’« Envoyer » d’un formulaire de contact ou au « Appliquer le code » d’un panier. Repos, chargement, terminé : les trois états qu’un formulaire attend d’un bouton de chargement.

Dans la démo, l’attente est simulée : reliez l’état de succès à votre vraie requête. Si l’enregistrement peut échouer, prévoyez aussi un état d’erreur. Afficher une coche alors que rien n’a été enregistré fait plus de mal que pas d’animation du tout.