Github

Spinner

An indicator that can be used to show a loading state.

Spinner SVG
Processing payment...Please wait
$100.00
import { Spinner } from "@/components/ui/spinner";
import { Surface } from "@/components/ui/surface";

export function SpinnerDemo() {
	return (
		<Surface className="flex w-full max-w-sm items-center gap-3 rounded-3xl p-6">
			<Spinner />
			<div className="flex min-w-0 flex-1 flex-col">
				<span className="truncate font-medium text-base">
					Processing payment...
				</span>
				<span className="text-muted-foreground text-sm">Please wait</span>
			</div>
			<span className="text-base tabular-nums">$100.00</span>
		</Surface>
	);
}

Installation

pnpm dlx shadcn@latest add https://herocn.dev/r/spinner.json

Usage

import { Spinner } from "@/components/ui/spinner"
<Spinner />

Examples

Variants

Use the variant prop to match the spinner color to your status or intent.

Spinner SVGSpinner SVGSpinner SVGSpinner SVGSpinner SVG
import { Spinner } from "@/components/ui/spinner";

export function SpinnerVariants() {
	return (
		<div className="flex flex-wrap items-center gap-6">
			<Spinner variant="default" />
			<Spinner variant="primary" />
			<Spinner variant="success" />
			<Spinner variant="warning" />
			<Spinner variant="destructive" />
		</div>
	);
}

Size

Use the size prop from the HeroCN spinner (sm, default, lg, xl).

Spinner SVGSpinner SVGSpinner SVGSpinner SVG
import { Spinner } from "@/components/ui/spinner";

export function SpinnerSizes() {
	return (
		<div className="flex items-center gap-6">
			<Spinner size="sm" />
			<Spinner size="default" />
			<Spinner size="lg" />
			<Spinner size="xl" />
		</div>
	);
}

Button

Add a spinner to a button to indicate a loading state. Place the spinner before the label with data-icon="inline-start" or after it with data-icon="inline-end".

import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";

export function SpinnerButton() {
	return (
		<div className="flex flex-col items-center gap-4">
			<Button disabled size="sm">
				<Spinner data-icon="inline-start" size="sm" />
				Loading...
			</Button>
			<Button variant="outline" disabled size="sm">
				Please wait
				<Spinner data-icon="inline-end" size="sm" />
			</Button>
			<Button variant="secondary" disabled size="sm">
				<Spinner data-icon="inline-start" size="sm" />
				Processing
			</Button>
		</div>
	);
}

Badge

Add a spinner to a badge to indicate background activity.

Spinner SVGSyncingUpdatingSpinner SVGSpinner SVGProcessing
import { Badge } from "@/components/ui/badge";
import { Spinner } from "@/components/ui/spinner";

export function SpinnerBadge() {
	return (
		<div className="flex items-center gap-4">
			<Badge>
				<Spinner data-icon="inline-start" size="sm" />
				Syncing
			</Badge>
			<Badge variant="primary">
				Updating
				<Spinner data-icon="inline-end" size="sm" />
			</Badge>
			<Badge variant="warning">
				<Spinner data-icon="inline-start" size="sm" />
				Processing
			</Badge>
		</div>
	);
}

Input Group

Use a spinner in input addons to indicate async validation or processing.

Spinner SVG
Spinner SVG Validating...
import { ArrowUpIcon } from "lucide-react";

import {
	InputGroup,
	InputGroupAddon,
	InputGroupButton,
	InputGroupInput,
	InputGroupTextarea,
} from "@/components/ui/input-group";
import { Spinner } from "@/components/ui/spinner";

export function SpinnerInputGroup() {
	return (
		<div className="flex w-full max-w-md flex-col gap-4">
			<InputGroup>
				<InputGroupInput placeholder="Send a message..." disabled />
				<InputGroupAddon align="inline-end">
					<Spinner size="sm" />
				</InputGroupAddon>
			</InputGroup>
			<InputGroup>
				<InputGroupTextarea placeholder="Send a message..." disabled />
				<InputGroupAddon align="block-end">
					<Spinner size="sm" /> Validating...
					<InputGroupButton disabled className="ml-auto" variant="default">
						<ArrowUpIcon />
						<span className="sr-only">Send</span>
					</InputGroupButton>
				</InputGroupAddon>
			</InputGroup>
		</div>
	);
}

Empty

Use a spinner in empty states when data is loading.

Spinner SVG
Processing your request
Please wait while we process your request. Do not refresh the page.
import { Button } from "@/components/ui/button";
import {
	Empty,
	EmptyContent,
	EmptyDescription,
	EmptyHeader,
	EmptyMedia,
	EmptyTitle,
} from "@/components/ui/empty";
import { Spinner } from "@/components/ui/spinner";

export function SpinnerEmpty() {
	return (
		<Empty className="w-full">
			<EmptyHeader>
				<EmptyMedia variant="icon">
					<Spinner />
				</EmptyMedia>
				<EmptyTitle>Processing your request</EmptyTitle>
				<EmptyDescription>
					Please wait while we process your request. Do not refresh the page.
				</EmptyDescription>
			</EmptyHeader>
			<EmptyContent>
				<Button variant="outline" size="sm">
					Cancel
				</Button>
			</EmptyContent>
		</Empty>
	);
}

RTL

Spinner SVG
جاري معالجة الدفع...
١٠٠.٠٠ دولار
"use client";

import {
	type Translations,
	useTranslation,
} from "@/components/language-selector";
import { Spinner } from "@/components/ui/spinner";
import { Surface } from "@/components/ui/surface";

const translations: Translations = {
	en: {
		dir: "ltr",
		values: {
			title: "Processing payment...",
			amount: "$100.00",
		},
	},
	ar: {
		dir: "rtl",
		values: {
			title: "جاري معالجة الدفع...",
			amount: "١٠٠.٠٠ دولار",
		},
	},
	he: {
		dir: "rtl",
		values: {
			title: "מעבד תשלום...",
			amount: "$100.00",
		},
	},
};

export function SpinnerRtl() {
	const { dir, language, t } = useTranslation(translations, "ar");

	return (
		<Surface
			className="flex w-full max-w-sm items-center gap-3 rounded-3xl p-6"
			lang={language}
			dir={dir}
		>
			<Spinner />
			<div className="flex min-w-0 flex-1 flex-col">
				<span className="truncate font-medium text-sm">{t.title}</span>
			</div>
			<span className="text-sm tabular-nums">{t.amount}</span>
		</Surface>
	);
}

API Reference

Spinner

The Spinner component accepts all native SVG props plus the following:

PropTypeDefault