Cli Loaders
19 braille-glyph animated spinners with badge, button, text, trail, and overlay composites. Accessible, zero external dependencies.
All 19 Spinners
Single glyph
⠋Loadingbraille
⠃Loadingorbit
⠀Loadingbreathe
Wave / flow
⠁⠂⠄⡀Loadingbraillewave
⠋⠉⠙⠚Loadingdna
⠖⠉⠉⠑Loadingwaverows
⢌⣉⢎⣉Loadinghelix
Fill / sweep
⣀⣀Loadingfillsweep
⠁⠀Loadingdiagswipe
⠉⠉⠉Loadingscanline
⠀⠀⠀⠀Loadingline
⠀⠀⠀⠀Loadingcascade
Grid patterns
⠀⠀⠀⠀Loadingscan
⢁⠂⠔⠈Loadingrain
⠀⠶⠀Loadingpulse
⣁⡀Loadingsnake
⡡⠊⢔⠡Loadingsparkle
⡀⠀⠀Loadingcolumns
⢕⢕⢕Loadingcheckerboard
Text Variants
⠋LoadingLoading data…⠋⠉⠙⠚LoadingGenerating…⠀⠀⠀⠀LoadingProcessing…
⠁⠂⠄⡀LoadingThinking⠖⠉⠉⠑LoadingUploading⠃LoadingLIVE⠃Loading
Badges
⠋LiveLive⠋SyncingSyncing⠋PendingPending⠋ErrorError⠋LoadingLoading
⠀⠀⠀⠀DeployingDeploying⡡⠊⢔⠡AnalyzingAnalyzing⠀⠀⠀⠀IndexingIndexing
Button
Trail Effect
⠦⠧⠇⠏⠋⠒⠲⠴⠤⠲⠴⠤⠄⠴⠤⠄⠋⠤⠄⠋⠉⠄⠋⠉⠙⠋⠉⠙⠚⠀⠀⢀⡴⠀⠀⠀⣠⠀⠀⠀⢀⠀⠀⠀⠀
Speed Variants
⠁⠂⠄⡀Loading0.5×
⠁⠂⠄⡀Loading1×
⠁⠂⠄⡀Loading2×
⠁⠂⠄⡀Loading4×
Size Variants
⢌⣉⢎⣉Loading0.75rem
⢌⣉⢎⣉Loading1rem
⢌⣉⢎⣉Loading1.25rem
⢌⣉⢎⣉Loading1.5rem
⢌⣉⢎⣉Loading2rem
⢌⣉⢎⣉Loading3rem
Live Preview
⠋Loading
braille
Click any spinner name above to preview
"use client";
import { useState } from "react";
import {
Spinner,
SpinnerInline,
SpinnerText,
SpinnerTextBookend,
SpinnerBadge,
SpinnerButton,
SpinnerTrail,
spinnerNames,
} from "@/components/vritti/cli-loaders";
import type { SpinnerName } from "@/components/vritti/cli-loaders";
const GROUPS: { label: string; names: SpinnerName[] }[] = [
{ label: "Single glyph", names: ["braille", "orbit", "breathe"] },
{ label: "Wave / flow", names: ["braillewave", "dna", "waverows", "helix"] },
{
label: "Fill / sweep",
names: ["fillsweep", "diagswipe", "scanline", "line", "cascade"],
},
{
label: "Grid patterns",
names: ["scan", "rain", "pulse", "snake", "sparkle", "columns", "checkerboard"],
},
];
const BADGE_COLORS = [
{ color: "#00ff99", label: "Live" },
{ color: "#7c3aed", label: "Syncing" },
{ color: "#f59e0b", label: "Pending" },
{ color: "#ef4444", label: "Error" },
{ color: "#38bdf8", label: "Loading" },
];
export function CliLoadersExample() {
const [loading, setLoading] = useState(false);
const [selectedSpinner, setSelectedSpinner] = useState<SpinnerName>("braille");
const handleLoadingToggle = () => {
setLoading(true);
setTimeout(() => setLoading(false), 2500);
};
return (
<div className="p-6 space-y-10 font-mono">
{/* All Spinners Grid */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
All 19 Spinners
</h3>
{GROUPS.map((group) => (
<div key={group.label} className="mb-6">
<p className="text-[11px] text-muted-foreground/60 mb-3 font-sans">
{group.label}
</p>
<div className="flex flex-wrap gap-4">
{group.names.map((name) => (
<div
key={name}
className="flex items-center gap-2 cursor-pointer"
onClick={() => setSelectedSpinner(name)}
>
<Spinner
name={name}
size="1.1rem"
color={
selectedSpinner === name
? "hsl(var(--brand))"
: "currentColor"
}
/>
<span className="text-xs text-muted-foreground">{name}</span>
</div>
))}
</div>
</div>
))}
</section>
{/* Inline + Text variants */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
Text Variants
</h3>
<div className="space-y-3">
<div className="flex items-center gap-6 flex-wrap">
<SpinnerInline name="braille" size="0.9rem">
Loading data…
</SpinnerInline>
<SpinnerInline name="dna" size="0.9rem" color="#7c3aed">
Generating…
</SpinnerInline>
<SpinnerInline name="cascade" size="0.9rem" color="#00ff99">
Processing…
</SpinnerInline>
</div>
<div className="flex items-center gap-6 flex-wrap">
<SpinnerText name="braillewave" text="Thinking" size="0.9rem" />
<SpinnerText name="waverows" text="Uploading" size="0.9rem" color="#f59e0b" />
<SpinnerTextBookend
name="orbit"
text="LIVE"
size="0.8rem"
color="#ef4444"
/>
</div>
</div>
</section>
{/* Badges */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
Badges
</h3>
<div className="flex flex-wrap gap-3">
{BADGE_COLORS.map(({ color, label }) => (
<SpinnerBadge
key={label}
label={label}
name="braille"
color={color}
size="0.75em"
/>
))}
</div>
<div className="flex flex-wrap gap-3 mt-3">
<SpinnerBadge label="Deploying" name="cascade" color="#38bdf8" size="0.75em" />
<SpinnerBadge label="Analyzing" name="sparkle" color="#a855f7" size="0.75em" />
<SpinnerBadge label="Indexing" name="scan" color="#f97316" size="0.75em" />
</div>
</section>
{/* Buttons */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
Button
</h3>
<div className="flex flex-wrap gap-4 items-center">
<SpinnerButton
loading={loading}
onClick={handleLoadingToggle}
spinnerProps={{ name: "braille", color: "inherit" }}
className="inline-flex items-center gap-2 px-4 py-2 text-sm rounded-lg bg-primary text-primary-foreground font-sans font-medium disabled:opacity-70"
style={{ fontFamily: "inherit" }}
>
{loading ? "Saving…" : "Save Changes"}
</SpinnerButton>
<SpinnerButton
loading={loading}
spinnerPosition="right"
spinnerProps={{ name: "orbit", color: "inherit" }}
className="inline-flex items-center gap-2 px-4 py-2 text-sm rounded-lg border border-border font-sans font-medium disabled:opacity-70"
style={{ fontFamily: "inherit" }}
>
{loading ? "Processing" : "Submit"}
</SpinnerButton>
</div>
</section>
{/* Trails */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
Trail Effect
</h3>
<div className="space-y-3">
<div className="flex flex-wrap gap-8 items-center">
<SpinnerTrail name="braille" trailLength={5} size="1.2rem" />
<SpinnerTrail
name="dna"
trailLength={6}
size="1.2rem"
color="#7c3aed"
/>
<SpinnerTrail
name="cascade"
trailLength={4}
size="1.2rem"
color="#00ff99"
reverse
/>
</div>
</div>
</section>
{/* Speed variants */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
Speed Variants
</h3>
<div className="flex flex-wrap gap-8 items-center">
<div className="flex flex-col items-center gap-1.5">
<Spinner name="braillewave" size="1.2rem" speed={0.5} />
<span className="text-[10px] text-muted-foreground">0.5×</span>
</div>
<div className="flex flex-col items-center gap-1.5">
<Spinner name="braillewave" size="1.2rem" speed={1} />
<span className="text-[10px] text-muted-foreground">1×</span>
</div>
<div className="flex flex-col items-center gap-1.5">
<Spinner name="braillewave" size="1.2rem" speed={2} />
<span className="text-[10px] text-muted-foreground">2×</span>
</div>
<div className="flex flex-col items-center gap-1.5">
<Spinner name="braillewave" size="1.2rem" speed={4} />
<span className="text-[10px] text-muted-foreground">4×</span>
</div>
</div>
</section>
{/* Size variants */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
Size Variants
</h3>
<div className="flex flex-wrap gap-6 items-end">
{["0.75rem", "1rem", "1.25rem", "1.5rem", "2rem", "3rem"].map(
(size) => (
<div key={size} className="flex flex-col items-center gap-1.5">
<Spinner name="helix" size={size} />
<span className="text-[10px] text-muted-foreground">{size}</span>
</div>
)
)}
</div>
</section>
{/* Interactive picker */}
<section>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
Live Preview
</h3>
<div className="flex items-center gap-6 p-4 rounded-xl border border-border bg-muted/30">
<Spinner name={selectedSpinner} size="2rem" color="hsl(var(--brand))" />
<div>
<p className="text-sm font-medium">{selectedSpinner}</p>
<p className="text-xs text-muted-foreground">
Click any spinner name above to preview
</p>
</div>
</div>
<div className="flex flex-wrap gap-2 mt-3">
{spinnerNames.map((name) => (
<button
key={name}
onClick={() => setSelectedSpinner(name)}
className={`px-2.5 py-1 text-[11px] rounded border transition-colors ${
selectedSpinner === name
? "border-primary bg-primary/10 text-primary"
: "border-border text-muted-foreground hover:border-primary/50"
}`}
>
{name}
</button>
))}
</div>
</section>
</div>
);
}
Installation
pnpm dlx shadcn@latest add "https://vritti.thesatyajit.com/r/cli-loaders"