Smart Loading Button
Intelligent loading button with progress indication, success states, and error handling.
ReactTailwind CSSTypeScriptResponsiveButton
Live Preview
Interactive
Smart Loading Button.tsxLive Code
"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>
);
}
How to use
- 1. Copy the component code
- 2. Paste it into your React/Next.js project
- 3. Make sure you have Tailwind CSS configured
- 4. Customize colors, spacing, and content as needed
Dependencies
react
^18.0.0tailwindcss
^3.0.0Back to Components
Need complete templates?
View Premium Templates