Skip to main content

Spinner

Show active processes. Simple, clear feedback. Prevent duplicate actions.

Loading invoices

Pulling the last 12 months from Stripe.

1import { Spinner } from '@/components/ui/spinner';
2
3export function Default() {
4 return (
5 <div className="border-border bg-card flex w-full max-w-sm flex-col items-center gap-3 rounded-xl border px-6 py-10">
6 <Spinner />
7 <div className="text-center">
8 <p className="text-sm font-medium">Loading invoices</p>
9 <p className="text-muted-foreground text-xs">Pulling the last 12 months from Stripe.</p>
10 </div>
11 </div>
12 );
13}

Installation

pnpm dlx nachui add spinner

Anatomy

1import { Spinner } from '@/components/ui/spinner';
1<Spinner />

Examples

Default

Loading invoices

Pulling the last 12 months from Stripe.

1import { Spinner } from '@/components/ui/spinner';
2
3export function Default() {
4 return (
5 <div className="border-border bg-card flex w-full max-w-sm flex-col items-center gap-3 rounded-xl border px-6 py-10">
6 <Spinner />
7 <div className="text-center">
8 <p className="text-sm font-medium">Loading invoices</p>
9 <p className="text-muted-foreground text-xs">Pulling the last 12 months from Stripe.</p>
10 </div>
11 </div>
12 );
13}

Sizes

You can change the size of the spinner using the size prop.

Table cell

Card body

Side panel

Full page

1import { Spinner } from '@/components/ui/spinner';
2
3const sizes = [
4 { size: 'sm' as const, usage: 'Table cell' },
5 { size: 'md' as const, usage: 'Card body' },
6 { size: 'lg' as const, usage: 'Side panel' },
7 { size: 'xl' as const, usage: 'Full page' },
8];
9
10export function Sizes() {
11 return (
12 <div className="grid w-full max-w-md grid-cols-4 gap-3">
13 {sizes.map((entry) => (
14 <div
15 key={entry.size}
16 className="border-border bg-card flex h-28 flex-col items-center justify-center gap-3 rounded-lg border p-3"
17 >
18 <Spinner size={entry.size} />
19 <p className="text-muted-foreground text-center text-xs">{entry.usage}</p>
20 </div>
21 ))}
22 </div>
23 );
24}

Variants

You can change the color of the spinner using the variant prop.

Building web-app

step 3 of 5

Queued: docs site

waiting for a runner

Promoting to production

almost done

Syncing environment variables

18 of 24

Retrying image upload

attempt 2 of 3

Rolling back api-gateway

triggered by CI

1import { Spinner } from '@/components/ui/spinner';
2
3const jobs = [
4 { variant: 'default' as const, label: 'Building web-app', detail: 'step 3 of 5' },
5 { variant: 'muted' as const, label: 'Queued: docs site', detail: 'waiting for a runner' },
6 { variant: 'success' as const, label: 'Promoting to production', detail: 'almost done' },
7 { variant: 'info' as const, label: 'Syncing environment variables', detail: '18 of 24' },
8 { variant: 'warning' as const, label: 'Retrying image upload', detail: 'attempt 2 of 3' },
9 { variant: 'destructive' as const, label: 'Rolling back api-gateway', detail: 'triggered by CI' },
10];
11
12export function Variants() {
13 return (
14 <div className="border-border bg-card divide-border w-full max-w-md divide-y rounded-xl border">
15 {jobs.map((job) => (
16 <div key={job.variant} className="flex items-center gap-3 px-4 py-3">
17 <Spinner size="sm" variant={job.variant} />
18 <p className="min-w-0 flex-1 truncate text-sm">{job.label}</p>
19 <p className="text-muted-foreground shrink-0 text-xs">{job.detail}</p>
20 </div>
21 ))}
22 </div>
23 );
24}

API Reference

Spinner

PropTypeDefaultDescription
size'sm' | 'md' | 'lg' | 'xl''md'The size of the spinner
variant'default' | 'primary' | 'muted' | 'success' | 'destructive' | 'warning' | 'info''default'Visual style
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue
Ctrl+I