ComponentsButton Loading
Button Loading
Terminal
npm i motion clsx tailwind-merge
utils/cn.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
ButtonLoading.tsx
"use client"; // @NOTE: add in case you are using Next.js
import { cn } from "@/utils/cn";
import { motion } from "motion/react";
export function ButtonLoading() {
return (
<button
disabled
className="mx-auto flex items-center gap-2 rounded-xl bg-black px-4 py-2 text-sm font-semibold duration-300 disabled:cursor-not-allowed disabled:opacity-80 dark:bg-white"
>
<Spinner />
<TextShine />
</button>
);
}
function Spinner() {
const bars = Array(12).fill(0);
return (
<div className="h-[18px] w-[18px]">
<div className="relative left-1/2 top-1/2 h-[inherit] w-[inherit]">
{bars.map((_, i) => (
<div
key={`spinner-bar-${i}`}
aria-label={`spinner-bar-${i + 1}`}
className={cn(
"absolute -left-[10%] -top-[3.9%] h-[8%] w-[24%] animate-spinner rounded-md bg-white dark:bg-black",
`bar:nth-child(${i + 1}`,
)}
style={{
animationDelay: `-${1.3 - i * 0.1}s`,
transform: `rotate(${30 * i}deg) translate(146%)`,
}}
/>
))}
</div>
</div>
);
}
function TextShine() {
return (
<motion.h1
className="bg-[linear-gradient(110deg,#696969,35%,#fff,50%,#696969,75%,#696969)] bg-[length:200%_100%] bg-clip-text text-sm font-medium text-transparent dark:bg-[linear-gradient(110deg,#747373,35%,#000,50%,#747373,75%,#747373)]"
initial={{ backgroundPosition: "200% 0" }}
animate={{ backgroundPosition: "-200% 0" }}
transition={{
repeat: Infinity,
duration: 2,
ease: "linear",
}}
>
Loading...
</motion.h1>
);
}
tailwind.config.ts
{
"animation": {
"spinner": "spinner 1.2s linear infinite",
"shine": "shine 2s linear infinite"
},
"keyframes": {
"spinner": {
"0%": {
"opacity": "1"
},
"100%": {
"opacity": "0.15"
}
},
"shine": {
"from": {
"backgroundPosition": "0 0"
},
"to": {
"backgroundPosition": "-200% 0"
}
}
}
}