Loading Spinner
Loading Spinner is a React and Tailwind CSS feedback component from Prompt Kit, licensed MIT. Copy it and paste it into your project.
Prompt Kit
MIT
feedback
What it is
Loading Spinner is a React and Tailwind CSS feedback component from Prompt Kit, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
No packages beyond the project baseline
Licence
This component comes from Prompt Kit and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Loading Spinner
"use client"
import { cn } from "@/lib/utils"
import React from "react"
export interface LoaderProps {
variant?:
| "circular"
| "classic"
| "pulse"
| "pulse-dot"
| "dots"
| "typing"
| "wave"
| "bars"
| "terminal"
| "text-blink"
| "text-shimmer"
| "loading-dots"
size?: "sm" | "md" | "lg"
text?: string
className?: string
}
export function CircularLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const sizeClasses = {
sm: "size-4",
md: "size-5",
lg: "size-6",
}
return (
<div
className={cn(
"border-primary animate-spin rounded-full border-2 border-t-transparent",
sizeClasses[size],
className
)}
>
<span className="sr-only">Loading</span>
</div>
)
}
export function ClassicLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const sizeClasses = {
sm: "size-4",
md: "size-5",
lg: "size-6",
}
const barSizes = {
sm: { height: "6px", width: "1.5px" },
md: { height: "8px", width: "2px" },
lg: { height: "10px", width: "2.5px" },
}
return (
<div className={cn("relative", sizeClasses[size], className)}>
<div className="absolute h-full w-full">
{[...Array(12)].map((_, i) => (
<div
key={i}
className="bg-primary absolute animate-[spinner-fade_1.2s_linear_infinite] rounded-full"
style={{
top: "0",
left: "50%",
marginLeft:
size === "sm" ? "-0.75px" : size === "lg" ? "-1.25px" : "-1px",
transformOrigin: `${size === "sm" ? "0.75px" : size === "lg" ? "1.25px" : "1px"} ${size === "sm" ? "10px" : size === "lg" ? "14px" : "12px"}`,
transform: `rotate(${i * 30}deg)`,
opacity: 0,
animationDelay: `${i * 0.1}s`,
height: barSizes[size].height,
width: barSizes[size].width,
}}
/>
))}
</div>
<span className="sr-only">Loading</span>
</div>
)
}
export function PulseLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const sizeClasses = {
sm: "size-4",
md: "size-5",
lg: "size-6",
}
return (
<div className={cn("relative", sizeClasses[size], className)}>
<div className="border-primary absolute inset-0 animate-[thin-pulse_1.5s_ease-in-out_infinite] rounded-full border-2" />
<span className="sr-only">Loading</span>
</div>
)
}
export function PulseDotLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const sizeClasses = {
sm: "size-1",
md: "size-2",
lg: "size-3",
}
return (
<div
className={cn(
"bg-primary animate-[pulse-dot_1.2s_ease-in-out_infinite] rounded-full",
sizeClasses[size],
className
)}
>
<span className="sr-only">Loading</span>
</div>
)
}
export function DotsLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const dotSizes = {
sm: "h-1.5 w-1.5",
md: "h-2 w-2",
lg: "h-2.5 w-2.5",
}
const containerSizes = {
sm: "h-4",
md: "h-5",
lg: "h-6",
}
return (
<div
className={cn(
"flex items-center space-x-1",
containerSizes[size],
className
)}
>
{[...Array(3)].map((_, i) => (
<div
key={i}
className={cn(
"bg-primary animate-[bounce-dots_1.4s_ease-in-out_infinite] rounded-full",
dotSizes[size]
)}
style={{
animationDelay: `${i * 160}ms`,
}}
/>
))}
<span className="sr-only">Loading</span>
</div>
)
}
export function TypingLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const dotSizes = {
sm: "h-1 w-1",
md: "h-1.5 w-1.5",
lg: "h-2 w-2",
}
const containerSizes = {
sm: "h-4",
md: "h-5",
lg: "h-6",
}
return (
<div
className={cn(
"flex items-center space-x-1",
containerSizes[size],
className
)}
>
{[...Array(3)].map((_, i) => (
<div
key={i}
className={cn(
"bg-primary animate-[typing_1s_infinite] rounded-full",
dotSizes[size]
)}
style={{
animationDelay: `${i * 250}ms`,
}}
/>
))}
<span className="sr-only">Loading</span>
</div>
)
}
export function WaveLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const barWidths = {
sm: "w-0.5",
md: "w-0.5",
lg: "w-1",
}
const containerSizes = {
sm: "h-4",
md: "h-5",
lg: "h-6",
}
const heights = {
sm: ["6px", "9px", "12px", "9px", "6px"],
md: ["8px", "12px", "16px", "12px", "8px"],
lg: ["10px", "15px", "20px", "15px", "10px"],
}
return (
<div
className={cn(
"flex items-center gap-0.5",
containerSizes[size],
className
)}
>
{[...Array(5)].map((_, i) => (
<div
key={i}
className={cn(
"bg-primary animate-[wave_1s_ease-in-out_infinite] rounded-full",
barWidths[size]
)}
style={{
animationDelay: `${i * 100}ms`,
height: heights[size][i],
}}
/>
))}
<span className="sr-only">Loading</span>
</div>
)
}
export function BarsLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const barWidths = {
sm: "w-1",
md: "w-1.5",
lg: "w-2",
}
const containerSizes = {
sm: "h-4 gap-1",
md: "h-5 gap-1.5",
lg: "h-6 gap-2",
}
return (
<div className={cn("flex", containerSizes[size], className)}>
{[...Array(3)].map((_, i) => (
<div
key={i}
className={cn(
"bg-primary h-full animate-[wave-bars_1.2s_ease-in-out_infinite]",
barWidths[size]
)}
style={{
animationDelay: `${i * 0.2}s`,
}}
/>
))}
<span className="sr-only">Loading</span>
</div>
)
}
export function TerminalLoader({
className,
size = "md",
}: {
className?: string
size?: "sm" | "md" | "lg"
}) {
const cursorSizes = {
sm: "h-3 w-1.5",
md: "h-4 w-2",
lg: "h-5 w-2.5",
}
const textSizes = {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
}
const containerSizes = {
sm: "h-4",
md: "h-5",
lg: "h-6",
}
return (
<div
className={cn(
"flex items-center space-x-1",
containerSizes[size],
className
)}
>
<span className={cn("text-primary font-mono", textSizes[size])}>
{">"}
</span>
<div
className={cn(
"bg-primary animate-[blink_1s_step-end_infinite]",
cursorSizes[size]
)}
/>
<span className="sr-only">Loading</span>
</div>
)
}
export function TextBlinkLoader({
text = "Thinking",
className,
size = "md",
}: {
text?: string
className?: string
size?: "sm" | "md" | "lg"
}) {
const textSizes = {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
}
return (
<div
className={cn(
"animate-[text-blink_2s_ease-in-out_infinite] font-medium",
textSizes[size],
className
)}
>
{text}
</div>
)
}
export function TextShimmerLoader({
text = "Thinking",
className,
size = "md",
}: {
text?: string
className?: string
size?: "sm" | "md" | "lg"
}) {
const textSizes = {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
}
return (
<div
className={cn(
"bg-[linear-gradient(to_right,var(--muted-foreground)_40%,var(--foreground)_60%,var(--muted-foreground)_80%)]",
"bg-size-[200%_auto] bg-clip-text font-medium text-transparent",
"animate-[shimmer_4s_infinite_linear]",
textSizes[size],
className
)}
>
{text}
</div>
)
}
export function TextDotsLoader({
className,
text = "Thinking",
size = "md",
}: {
className?: string
text?: string
size?: "sm" | "md" | "lg"
}) {
const textSizes = {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
}
return (
<div
className={cn("inline-flex items-center", className)}
>
<span className={cn("text-primary font-medium", textSizes[size])}>
{text}
</span>
<span className="inline-flex">
<span className="text-primary animate-[loading-dots_1.4s_infinite_0.2s]">
.
</span>
<span className="text-primary animate-[loading-dots_1.4s_infinite_0.4s]">
.
</span>
<span className="text-primary animate-[loading-dots_1.4s_infinite_0.6s]">
.
</span>
</span>
</div>
)
}
function Loader({
variant = "circular",
size = "md",
text,
className,
}: LoaderProps) {
switch (variant) {
case "circular":
return <CircularLoader size={size} className={className} />
case "classic":
return <ClassicLoader size={size} className={className} />
case "pulse":
return <PulseLoader size={size} className={className} />
case "pulse-dot":
return <PulseDotLoader size={size} className={className} />
case "dots":
return <DotsLoader size={size} className={className} />
case "typing":
return <TypingLoader size={size} className={className} />
case "wave":
return <WaveLoader size={size} className={className} />
case "bars":
return <BarsLoader size={size} className={className} />
case "terminal":
return <TerminalLoader size={size} className={className} />
case "text-blink":
return <TextBlinkLoader text={text} size={size} className={className} />
case "text-shimmer":
return <TextShimmerLoader text={text} size={size} className={className} />
case "loading-dots":
return <TextDotsLoader text={text} size={size} className={className} />
default:
return <CircularLoader size={size} className={className} />
}
}
export { Loader }