Animated spinner indicator with size variants
import { Spinner } from '@/components/ui/spinner'
...import { Spinner } from '@/components/ui/spinner'
export default function SpinnerDemoExample() { return <div class="flex w-80 items-center gap-3 rounded-lg border p-4"><Spinner /><div class="grid"><strong>Processing payment...</strong><span class="text-sm text-muted-foreground">$100.00</span></div></div> }
Installation
npx @fictjs/shadcn@latest add spinnerInstall the runtime dependencies:
npm install class-variance-authorityCopy the Fict shadcn component into your project.
Source
components/ui/spinner.tsx
src/components/ui/spinner.tsx
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/cn'
const spinnerVariants = cva('inline-block animate-spin rounded-full border-2 border-current border-r-transparent', {
variants: {
size: {
sm: 'h-3 w-3',
default: 'h-4 w-4',
lg: 'h-6 w-6',
},
},
defaultVariants: {
size: 'default',
},
})
type SpinnerProps = JSX.IntrinsicElements['span'] & VariantProps<typeof spinnerVariants>
export function Spinner(props: SpinnerProps) {
const { class: className, size, ...rest } = props
return <span role='status' aria-label='Loading' class={cn(spinnerVariants({ size }), className)} {...rest} />
}
Usage
Examples
Custom
import { Spinner } from '@/components/ui/spinner'
...import { Spinner } from '@/components/ui/spinner'
export default function SpinnerCustomExample() { return <Spinner aria-label="Loading" /> }
Size
import { Spinner } from '@/components/ui/spinner'
...import { Spinner } from '@/components/ui/spinner'
export default function SpinnerSizeExample() { return <div class="flex items-center gap-4"><Spinner size="sm" /><Spinner /><Spinner size="lg" /><Spinner class="size-8" /></div> }
Button
import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/spinner'
...import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/spinner'
export default function SpinnerButtonExample() { return <div class="flex gap-2"><Button disabled><Spinner />Loading...</Button><Button variant="outline" disabled><Spinner />Please wait</Button><Button variant="secondary" disabled><Spinner />Processing</Button></div> }
Badge
import { Badge } from '@/components/ui/badge'
import { Spinner } from '@/components/ui/spinner'
...import { Badge } from '@/components/ui/badge'
import { Spinner } from '@/components/ui/spinner'
export default function SpinnerBadgeExample() { return <div class="flex gap-2"><Badge><Spinner size="sm" />Syncing</Badge><Badge variant="secondary"><Spinner size="sm" />Updating</Badge><Badge variant="outline"><Spinner size="sm" />Processing</Badge></div> }
Input Group
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'
import { Spinner } from '@/components/ui/spinner'
...import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'
import { Spinner } from '@/components/ui/spinner'
export default function SpinnerInputGroupExample() { return <div class="grid w-80 gap-3"><InputGroup><InputGroupInput placeholder="Send a message..." disabled /><InputGroupAddon><Spinner /></InputGroupAddon></InputGroup><InputGroup class="items-end"><textarea class="min-h-20 flex-1 resize-none bg-transparent p-3" placeholder="Send a message..." disabled /><InputGroupAddon><Spinner />Validating...</InputGroupAddon></InputGroup></div> }
Empty
Processing your request
Please wait while we process your request. Do not refresh the page.
import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/spinner'
...import { Button } from '@/components/ui/button'
import { Spinner } from '@/components/ui/spinner'
export default function SpinnerEmptyExample() { return <div class="grid justify-items-center gap-3 text-center"><Spinner /><h3 class="font-semibold">Processing your request</h3><p class="text-sm text-muted-foreground">Please wait while we process your request. Do not refresh the page.</p><Button variant="outline">Cancel</Button></div> }
Rtl
import { Spinner } from '@/components/ui/spinner'
...import { Spinner } from '@/components/ui/spinner'
const translations = { ar: ['جاري معالجة الدفع...', '١٠٠.٠٠ دولار'], he: ['מעבד תשלום...', '$100.00'], en: ['Processing payment...', '$100.00'] } as const
export default function SpinnerRtlExample() { let language = $state<keyof typeof translations>('ar'); const direction = () => language === 'en' ? 'ltr' : 'rtl'; return <div class="grid gap-4"><select value={language} onChange={event => { language = event.currentTarget.value as keyof typeof translations }}><option value="ar">Arabic (العربية)</option><option value="he">Hebrew (עברית)</option><option value="en">English</option></select><div class="flex w-80 items-center gap-3 rounded-lg border p-4" dir={direction()}><Spinner /><div class="grid"><strong>{translations[language][0]}</strong><span>{translations[language][1]}</span></div></div></div> }