bloomtpl
  • Documentation
  • Contact
bloomtpl

The best Next.js templates for modern web applications.
Deploy faster, code less.

Your Feedback

Review us onTrustpilot

Templates

  • All Templates10+
  • Business Templates
  • E-Commerce Templates
  • Landing Page Templates
  • Blog Templates
  • Portfolio Templates
  • Free Templates

Resources

  • Docs
  • Contact
  • Blog
  • License

Follow us

Twitter

Legal

  • Privacy Policy
  • Refund Policy
  • Terms of Use
  • Legal Notice
© 2025 BloomTPL™. All Rights Reserved.
❤️Made with love for developers
ComponentsSmart Loading Button

Smart Loading Button

Intelligent loading button with progress indication, success states, and error handling.

ReactTailwind CSSTypeScriptResponsiveButton

Live Preview

Interactive
Smart Loading Button.tsxLive Code

How to use

  1. 1. Copy the component code
  2. 2. Paste it into your React/Next.js project
  3. 3. Make sure you have Tailwind CSS configured
  4. 4. Customize colors, spacing, and content as needed

Dependencies

react
tailwindcss
Back to Components

Need complete templates?

View Premium Templates
"use client";

import { useState } from "react";

export default function SmartLoadingButton() {
  const [state, setState] = useState<"idle" | "loading" | "success" | "error">(
    "idle"
  );
  const [progress, setProgress] = useState(0);

  const handleClick = async () => {
    setState("loading");
    setProgress(0);

    const interval = setInterval(() => {
      setProgress((prev) => {
        const newProgress = prev + Math.random() * 15;

        if (newProgress >= 100) {
          clearInterval(interval);
          setState("success");
          setTimeout(() => {
            setState("idle");
            setProgress(0);
          }, 2000);
          return 100;
        }
        return newProgress;
      });
    }, 150);

    setTimeout(() => {
      if (state === "loading") {
        clearInterval(interval);
        setState("error");
        setTimeout(() => {
          setState("idle");
          setProgress(0);
        }, 2000);
      }
    }, 8000);
  };

  const getButtonContent = () => {
    switch (state) {
      case "loading":
        return (
          <div className="flex items-center justify-center gap-2">
            <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin flex-shrink-0" \/>
            <span className="hidden sm:inline">Processing...<\/span>
            <span className="text-xs opacity-75 hidden md:inline">
              {Math.round(progress)}%
            <\/span>
          <\/div>
        );
      case "success":
        return (
          <div className="flex items-center justify-center gap-2">
            <svg
              className="w-4 h-4 flex-shrink-0"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M5 13l4 4L19 7"
              \/>
            <\/svg>
            <span className="hidden sm:inline">Success!<\/span>
            <span className="sm:hidden">\u2713<\/span>
          <\/div>
        );
      case "error":
        return (
          <div className="flex items-center justify-center gap-2">
            <svg
              className="w-4 h-4 flex-shrink-0"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M6 18L18 6M6 6l12 12"
              \/>
            <\/svg>
            <span className="hidden sm:inline">Try Again<\/span>
            <span className="sm:hidden">Retry<\/span>
          <\/div>
        );
      default:
        return (
          <span className="px-2">
            <span className="hidden sm:inline">Submit Form<\/span>
            <span className="sm:hidden">Submit<\/span>
          <\/span>
        );
    }
  };

  const getButtonClasses = () => {
    const baseClasses =
      "relative overflow-hidden px-4 sm:px-6 py-2 sm:py-3 rounded-lg font-medium transition-all duration-300 min-w-[120px] sm:min-w-[160px] md:min-w-[180px] text-sm sm:text-base focus:outline-none focus:ring-2 focus:ring-offset-2";

    switch (state) {
      case "success":
        return `${baseClasses} bg-green-600 text-white focus:ring-green-500 transform scale-105`;
      case "error":
        return `${baseClasses} bg-red-600 hover:bg-red-700 text-white focus:ring-red-500`;
      case "loading":
        return `${baseClasses} bg-purple-400 cursor-not-allowed text-white`;
      default:
        return `${baseClasses} bg-purple-600 hover:bg-purple-700 active:bg-purple-800 text-white focus:ring-purple-500 hover:transform hover:scale-[1.02]`;
    }
  };

  return (
    <div className="flex flex-col items-center gap-4 w-full max-w-sm mx-auto">
      <div className="flex justify-center w-full">
        <div className="relative">
          <button
            onClick={handleClick}
            disabled={state === "loading"}
            className={getButtonClasses()}
            aria-label={
              state === "loading"
                ? `Loading ${Math.round(progress)}%`
                : "Submit form"
            }
          >
            {state === "loading" && (
              <div
                className="absolute left-0 top-0 h-full bg-purple-700 transition-all duration-200 ease-out rounded-lg"
                style={{ width: `${progress}%` }}
              \/>
            )}

            <span className="relative z-10 flex items-center justify-center">
              {getButtonContent()}
            <\/span>
          <\/button>

          {state === "loading" && (
            <div className="mt-2 w-full bg-gray-200 rounded-full h-1.5 sm:hidden">
              <div
                className="bg-purple-600 h-1.5 rounded-full transition-all duration-200"
                style={{ width: `${progress}%` }}
              \/>
            <\/div>
          )}
        <\/div>
      <\/div>

      <div className="text-center min-h-[20px] text-xs sm:text-sm">
        {state === "idle" && (
          <p className="text-gray-500">
            <span className="hidden sm:inline">
              Click to simulate an async operation
            <\/span>
            <span className="sm:hidden">Click to start<\/span>
          <\/p>
        )}
        {state === "loading" && (
          <div className="space-y-1">
            <p className="text-purple-600 font-medium">
              <span className="hidden sm:inline">
                Processing your request...
              <\/span>
              <span className="sm:hidden">Processing...<\/span>
            <\/p>
            <p className="text-gray-500 text-xs hidden sm:block">
              Progress: {Math.round(progress)}% complete
            <\/p>
          <\/div>
        )}
        {state === "success" && (
          <p className="text-green-600 font-medium">
            <span className="hidden sm:inline">
              Operation completed successfully!
            <\/span>
            <span className="sm:hidden">Success!<\/span>
          <\/p>
        )}
        {state === "error" && (
          <p className="text-red-600 font-medium">
            <span className="hidden sm:inline">
              Something went wrong. Please try again.
            <\/span>
            <span className="sm:hidden">Error occurred<\/span>
          <\/p>
        )}
      <\/div>

      <div className="flex gap-2 text-xs">
        <button
          onClick={() => {
            setState("idle");
            setProgress(0);
          }}
          className="px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded text-gray-600 transition-colors"
          disabled={state === "loading"}
        >
          Reset
        <\/button>
        <button
          onClick={() => {
            setState("error");
            setTimeout(() => setState("idle"), 2000);
          }}
          className="px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded text-gray-600 transition-colors"
          disabled={state === "loading"}
        >
          <span className="hidden sm:inline">Simulate Error<\/span>
          <span className="sm:hidden">Error<\/span>
        <\/button>
      <\/div>
    <\/div>
  );
}