Feedback
Spinner.
A spinning orange loader, plus a block spinner that pulses three squares like a terminal waiting on a reply.
Connecting to hub
import { BlockSpinner, Spinner } from "@/components/ui/spinner";
export default function SpinnerDemo() {
return (
<div className="flex flex-col items-center gap-6">
<div className="flex items-center gap-6">
<Spinner />
<Spinner className="size-6" />
<Spinner className="size-8 text-success" />
</div>
<div className="flex items-center gap-3 font-mono text-[11px] tracking-[0.2em] text-muted-foreground uppercase">
<BlockSpinner />
Connecting to hub
</div>
</div>
);
}Installation#
With the CLI
$npx lanterncn add spinnerThis also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/spinner. See Installation if your project is not set up yet.
Manually
Install the dependencies
npm install lucide-reactCopy the source into your project
components/ui/spinner.tsximport * as React from "react"; import { Loader2Icon } from "lucide-react"; import { cn } from "@/lib/utils"; function Spinner({ className, ...props }: React.ComponentProps<"svg">) { return ( <Loader2Icon data-slot="spinner" role="status" aria-label="Loading" className={cn("size-4 animate-spin text-primary", className)} {...props} /> ); } /** A terminal-style spinner: three blocks lighting up in turn. */ function BlockSpinner({ className, ...props }: React.ComponentProps<"span">) { return ( <span data-slot="block-spinner" role="status" aria-label="Loading" className={cn("inline-flex items-center gap-[3px] text-primary", className)} {...props} > {[0, 1, 2].map((i) => ( <i key={i} aria-hidden="true" className="block size-1.5 animate-lantern-pulse bg-current" style={{ animationDuration: "0.9s", animationDelay: `${i * 0.15}s` }} /> ))} </span> ); } export { Spinner, BlockSpinner };Update the import paths to match your project
The source imports
cnfrom@/lib/utils.
Usage#
import { Spinner, BlockSpinner } from "@/components/ui/spinner";<Spinner />Both spinners announce themselves with role="status" and the label Loading. Inside a button that already says what is happening, pass aria-hidden and clear the role.
Examples#
In a button
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
export default function SpinnerButton() {
return (
<div className="flex flex-wrap items-center gap-3">
<Button disabled>
<Spinner className="text-current" aria-hidden="true" role={undefined} />
Publishing
</Button>
<Button variant="outline" disabled>
<Spinner aria-hidden="true" role={undefined} />
Syncing site
</Button>
</div>
);
}