Skip to main content
Urbicon UI

Spinner

Loading indicators with multiple animation styles, semantic colors, and configurable speed.

Playground

Loading...
Speed
<Spinner
  label="Loading..."
  visible
/>

01 Examples

In a list

The two places a list needs a spinner: on a single row while its action runs and the rest of the list stays interactive, and at the foot of the list while the next page is fetched.
design-tokens.md Synced
components.tsx
Syncing components.tsx
accessibility-audit.md Queued
  • Item 1 — Loaded
  • Item 2 — Loaded
  • Item 3 — Loaded
Loading...
Loading more…
<div
  class="border-border-subtle bg-surface-elevated divide-border-subtle w-full divide-y rounded-2xl border"
>
  <div class="flex items-center gap-3 px-4 py-3">
    <span class="text-text-primary flex-1 text-sm">design-tokens.md</span>
    <Badge intent="success" size="sm" variant="soft">Synced</Badge>
  </div>
  <div class="flex items-center gap-3 px-4 py-3">
    <span class="text-text-primary flex-1 text-sm">components.tsx</span>
    <Spinner size="sm" intent="primary" label="Syncing components.tsx" />
  </div>
  <div class="flex items-center gap-3 px-4 py-3">
    <span class="text-text-primary flex-1 text-sm">accessibility-audit.md</span>
    <Badge intent="neutral" size="sm" variant="soft">Queued</Badge>
  </div>
</div>

<div class="border-border-subtle bg-surface-elevated w-full rounded-2xl border p-4">
  <ul class="space-y-2.5">
    <li class="text-text-primary text-sm">Item 1 — Loaded</li>
    <li class="text-text-primary text-sm">Item 2 — Loaded</li>
    <li class="text-text-primary text-sm">Item 3 — Loaded</li>
  </ul>
  <div class="border-border-subtle mt-4 flex items-center justify-center gap-2 border-t pt-4">
    <Spinner size="sm" variant="dots" intent="primary" />
    <span class="text-text-tertiary text-xs">Loading more…</span>
  </div>
</div>

Color inheritance

Use intent='current' so the spinner picks up the surrounding text color via currentColor — ideal inside coloured surfaces, badges, or alerts where a fixed intent would clash.
Loading...
Deploying
Loading...
Retrying
Loading...
Rolling back
<div
  class="text-success-emphasis bg-success/15 inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-sm font-medium"
>
  <Spinner size="xs" intent="current" />
  Deploying
</div>
<div
  class="text-warning-emphasis bg-warning/15 inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-sm font-medium"
>
  <Spinner size="xs" intent="current" />
  Retrying
</div>
<div
  class="text-danger-emphasis bg-danger/15 inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-sm font-medium"
>
  <Spinner size="xs" intent="current" />
  Rolling back
</div>

Content Loading Overlay

Frosted glass overlay with centered spinner for in-place content loading.
Loading...

Loading content…

<div class="relative w-full max-w-sm overflow-hidden">
  <Card padding="lg">
    <div class="space-y-3 opacity-30 select-none" aria-hidden="true">
      <div class="bg-surface-subtle rounded-modify h-4 w-3/4"></div>
      <div class="bg-surface-subtle rounded-modify h-4 w-full"></div>
      <div class="bg-surface-subtle rounded-modify h-4 w-5/6"></div>
      <div class="bg-surface-subtle rounded-modify mt-4 h-8 w-1/3"></div>
    </div>
  </Card>
  <div
    class="bg-surface-base/60 rounded-contain absolute inset-0 z-[var(--z-docked)] flex flex-col items-center justify-center gap-3 backdrop-blur-sm"
  >
    <Spinner size="lg" intent="primary" variant="ring" />
    <p class="text-text-secondary text-sm font-medium">Loading content…</p>
  </div>
</div>

Status Pipeline

Spinners as part of a multi-step processing flow.
Files uploaded Done
Loading...
Processing data… Active
3
Deploy to production Pending
<div class="flex w-full max-w-md flex-col gap-4">
  <div class="flex items-center gap-3">
    <div
      class="bg-success/15 text-success flex h-8 w-8 items-center justify-center rounded-full"
    >
      <CheckIcon size={16} />
    </div>
    <span class="text-text-secondary text-sm">Files uploaded</span>
    <Badge intent="success" size="sm" variant="soft" class="ml-auto">Done</Badge>
  </div>
  <div class="flex items-center gap-3">
    <Spinner size="lg" intent="primary" variant="bars" speed="fast" />
    <span class="text-text-primary text-sm font-medium">Processing data…</span>
    <Badge intent="primary" size="sm" variant="soft" pulse class="ml-auto">Active</Badge>
  </div>
  <div class="flex items-center gap-3">
    <div
      class="bg-surface-subtle text-text-tertiary flex h-8 w-8 items-center justify-center rounded-full"
    >
      <span class="text-xs font-bold">3</span>
    </div>
    <span class="text-text-tertiary text-sm">Deploy to production</span>
    <Badge intent="neutral" size="sm" variant="soft" class="ml-auto">Pending</Badge>
  </div>
</div>

02 Customization

Slot Overrides

Use slotClasses to restyle individual parts without unstyled mode.
Loading...
Loading...
Loading...
<Spinner
  size="xl"
  slotClasses={{
    svgCircle: 'opacity-10',
    svgPath: 'fill-current drop-shadow-[0_0_6px_currentColor]'
  }}
/>
<Spinner variant="dots" size="xl" intent="success" slotClasses={{ dot: 'rounded-none' }} />
<Spinner variant="bars" size="xl" intent="danger" slotClasses={{ bar: 'rounded-full' }} />

Fully Custom (unstyled)

Drop all defaults and build a completely unique indicator.
Syncing
Connecting
Analyzing
<Spinner
  unstyled
  class="relative inline-flex h-10 w-10 items-center justify-center"
  slotClasses={{
    svg: 'w-full h-full animate-spin [animation-duration:0.8s]',
    svgCircle: 'opacity-0',
    svgPath: 'fill-sky-400 drop-shadow-[0_0_8px_rgba(56,189,248,0.5)]'
  }}
  label="Syncing"
/>
<Spinner
  variant="dots"
  unstyled
  class="inline-flex h-10 items-center gap-2"
  slotClasses={{
    dots: 'flex items-center gap-2',
    dot: 'h-2.5 w-2.5 rounded-full bg-emerald-400 animate-bounce [animation-duration:0.8s] shadow-[0_0_8px_rgba(52,211,153,0.5)]'
  }}
  label="Connecting"
/>
<Spinner
  variant="bars"
  unstyled
  class="inline-flex h-10 items-center"
  slotClasses={{
    bars: 'flex items-center gap-1',
    bar: 'w-1 h-7 rounded-full bg-violet-400 [animation-name:spinner-bars] [animation-duration:0.7s] [animation-timing-function:ease-in-out] [animation-iteration-count:infinite] [&:nth-child(1)]:delay-[-0.36s] [&:nth-child(2)]:delay-[-0.24s] [&:nth-child(3)]:delay-[-0.12s] shadow-[0_0_8px_rgba(167,139,250,0.5)]'
  }}
  label="Analyzing"
/>

A branded loader used across the app belongs in a BlocksProvider preset (presets.Spinner), applied via preset — see Customization.

03 Accessibility

Screen Reader

Uses role="status" with aria-label from the label prop (defaults to "Loading…"). The aria-live="polite" region announces loading state without interrupting the user.

Visibility

When visible={false}, the spinner is removed from the DOM entirely — no visual output and no accessibility tree entry.

Reduced Motion

All animations (spin, bounce, pulse, bars) respect prefers-reduced-motion: reduce via Tailwind's motion-reduce: variant. In reduced-motion mode the spinner still renders statically to indicate loading.

Print

Spinners are hidden from print output via a @media print rule.

04 API Reference

13 props
13 props
Prop
Type
Default
Description

05 Types

Local type definitions used by this component.

3 types
Name
Kind
Category
Used by
Description

06 Installation

Import

import { Spinner } from '@urbicon-ui/blocks';